{"openapi":"3.1.0","info":{"title":"Base44 App Management API","version":"1.0.0"},"paths":{"/api/apps/{app_id}/entities/User/count":{"get":{"summary":"Count app users","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns how many users the app has. These are the people who signed up to it or were invited to it.\n\nThe count covers the users the calling credential can read, so a `User` entity whose row-level security rules narrow reads returns a smaller number than the app actually has.\n\nIt also leaves out accounts Base44 added to the app on its own, such as workspace admins and support staff granted access, and the short-lived accounts a test run creates.","operationId":"count_users_api_apps__app_id__entities_User_count_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose users you want to count.","title":"App Id"},"description":"ID of the app whose users you want to count.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppUserCountResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded. This endpoint allows 100 requests per minute per app, shared with the app's other user listing calls."}}}},"/api/apps/{app_id}/entities/{entity_name}":{"get":{"summary":"List entity records","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the records held in one of the app's entities. Deleted records are left out.\n\nRow-level security applies, so an entity whose `rls` read rule narrows what a caller sees returns only the records that credential can read. An entity with no `rls` rules returns all of its records.\n\nFilter with `q`, or by passing one of the entity's own fields as a query parameter of that name. Any parameter this endpoint doesn't define is read as a filter, so a misspelled one narrows the result to nothing rather than failing the call.\n\nBase44 returns at most 5000 records per call. It caps the result at 5000 both when you leave `limit` out and when you ask for more, and the response doesn't tell you it was cut short, so page through a bigger entity with `skip`.\n\nRecords come back in no particular order unless you set `sort`.\n\nPass the entity name exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. The `User` entity is served by its own endpoints and never reaches this one.\n\nThis endpoint is limited to 100 requests per minute per app, shared with the app's other entity listing calls.\n\n<Note>This endpoint accepts a personal API key belonging to a user with access to the app, including a read-only key. Workspace API keys are not accepted.</Note>","operationId":"list_entities_api_apps__app_id__entities__entity_name__get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app that owns the entity.","title":"App Id"},"description":"ID of the app that owns the entity.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"entity_name","in":"path","required":true,"schema":{"type":"string","description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it.","example":"Invoice"},{"name":"q","in":"query","required":false,"description":"Filter as a JSON object of field names and values, for example `{\"status\": \"paid\"}`. A value can be a comparison rather than an exact match, as in `{\"amount\": {\"$gt\": 100}}`, and `$and`, `$or`, `$nor` and `$not` combine conditions. Keep the nesting shallow. Base44 wraps your filter in its own row-level-security conditions before it checks the depth, so how deep you can go depends on the entity's rules.","example":"{\"status\": \"paid\"}","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of records to return, as a whole number of at least 1. Defaults to 5000, and Base44 returns at most 5000 records however high you set this.","example":100,"schema":{"type":"integer","default":5000}},{"name":"skip","in":"query","required":false,"description":"Number of records to skip before the ones you get back, as a whole number of 0 or more. Defaults to 0. Use it with `limit` to page through an entity.","example":100,"schema":{"type":"integer","default":0}},{"name":"sort","in":"query","required":false,"description":"Single field to sort by, prefixed with `-` for descending. For example, `-created_date` returns newest first. Sorting by more than one field isn't supported.","example":"-created_date","schema":{"type":"string"}},{"name":"fields","in":"query","required":false,"description":"Comma-separated list of fields to return, which reduces the response size on a wide entity. Reach a field inside an object with dots, as in `customer.email`. Each record still carries its `id` whether you ask for it or not.","example":"status,amount","schema":{"type":"string"}}],"responses":{"200":{"description":"The entity's records.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true,"description":"One record in one of an app's entities.","properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ID of the record. Pass it as `entity_id` to [Get entity record](/api-reference/get-entity-record), [Update entity record](/api-reference/update-entity-record) or [Delete entity record](/api-reference/delete-entity-record).","example":"6886b8d390dc7e2f4a2c91b3","title":"Id"},"created_date":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the record was created, as a UTC timestamp in ISO 8601 format. A record Base44 has just created carries a `Z` suffix, and a record read back from storage does not.","example":"2026-06-01T09:23:41.481000","title":"Created Date"},"updated_date":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the record last changed, as a UTC timestamp in ISO 8601 format. A record Base44 has just created carries a `Z` suffix, and a record read back from storage does not.","example":"2026-06-04T14:07:02.115000","title":"Updated Date"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Email of the app user who created the record, or `anonymous` when a visitor created it on an app that needs no login. Apps that hide record authorship leave this field out of the response.","example":"jane@acme.com","title":"Created By"},"created_by_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ID of the app user who created the record, or `anonymous` when a visitor created it on an app that needs no login.","example":"6874b0c2e1a94d0031bb77de","title":"Created By Id"},"is_sample":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether Base44 stored the record as sample data while the app was being built. A record you create reports `false`.","example":false,"title":"Is Sample"}},"title":"EntityRecord"},"title":"EntityRecords"}}}},"400":{"description":"`sort` names more than one field."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App not found, or the app has no entity with this name."},"422":{"description":"A filter you passed as its own query parameter doesn't match the type the entity's schema declares for that field, for example text where the field holds a number. Values inside `q` aren't type-checked."},"429":{"description":"Rate limit exceeded. This endpoint allows 100 requests per minute per app, shared with the app's other entity listing calls."}}},"post":{"summary":"Create entity record","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nAdds a record to one of the app's entities and returns it.\n\nBase44 stores only the fields the entity's schema declares and drops the rest, so a misspelled name is left out of the record instead of failing the call. Base44 also assigns `id`, `created_date`, `updated_date`, `created_by` and `created_by_id`, and ignores them if you send them.\n\nRow-level security applies, so an entity whose `rls` create rule doesn't cover the record you send fails with a 403.\n\nDon't pass `User` as the `entity_name`. Base44 manages the app's users through its own endpoints, and a record created here under that name is stored where those endpoints don't read it.\n\nCreating a record triggers the app's webhooks and any automation or workflow that listens for this entity.\n\nThis endpoint is limited to 140 requests per minute per app.\n\n<Note>This endpoint accepts a personal API key belonging to a user with access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"create_entity_api_apps__app_id__entities__entity_name__post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app that owns the entity.","title":"App Id"},"description":"ID of the app that owns the entity.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"entity_name","in":"path","required":true,"schema":{"type":"string","description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it.","example":"Invoice"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"CreateEntityRecord","description":"The record's fields, as a flat JSON object using the names the entity's [schema](/api-reference/get-entity-schema) declares."},"example":{"amount":4200,"status":"draft","customer_email":"jane@acme.com"}}}},"responses":{"200":{"description":"The created record.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"EntityRecord","description":"One record in one of an app's entities.","properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ID of the record. Pass it as `entity_id` to [Get entity record](/api-reference/get-entity-record), [Update entity record](/api-reference/update-entity-record) or [Delete entity record](/api-reference/delete-entity-record).","example":"6886b8d390dc7e2f4a2c91b3","title":"Id"},"created_date":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the record was created, as a UTC timestamp in ISO 8601 format. A record Base44 has just created carries a `Z` suffix, and a record read back from storage does not.","example":"2026-06-01T09:23:41.481000","title":"Created Date"},"updated_date":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the record last changed, as a UTC timestamp in ISO 8601 format. A record Base44 has just created carries a `Z` suffix, and a record read back from storage does not.","example":"2026-06-04T14:07:02.115000","title":"Updated Date"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Email of the app user who created the record, or `anonymous` when a visitor created it on an app that needs no login. Apps that hide record authorship leave this field out of the response.","example":"jane@acme.com","title":"Created By"},"created_by_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ID of the app user who created the record, or `anonymous` when a visitor created it on an app that needs no login.","example":"6874b0c2e1a94d0031bb77de","title":"Created By Id"},"is_sample":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether Base44 stored the record as sample data while the app was being built. A record you create reports `false`.","example":false,"title":"Is Sample"}}}}}},"400":{"description":"A field value is longer than 20,000 characters."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the entity's `rls` create rule doesn't cover the record, or your API key is read-only."},"404":{"description":"App not found, or the app has no entity with this name."},"422":{"description":"The body is missing, or a value doesn't match the type the entity's schema declares for that field."},"429":{"description":"Rate limit exceeded. This endpoint allows 140 requests per minute per app."}}}},"/api/apps/{app_id}/entities/{entity_name}/count":{"get":{"summary":"Count entity records","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns how many production records an entity holds.\n\nThe count covers the records the calling credential can read, so an entity whose row-level security rules narrow reads returns a smaller number than the entity actually holds. An entity with no `rls` rules returns its full count.\n\nDon't pass `User` as the `entity_name`. To count the app's users, use [Count app users](/api-reference/count-app-users) instead.","operationId":"count_entities_api_apps__app_id__entities__entity_name__count_get","parameters":[{"name":"entity_name","in":"path","required":true,"schema":{"type":"string","description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it.","example":"Invoice"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose entity records you want to count.","title":"App Id"},"description":"ID of the app whose entity records you want to count.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntityCountResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app."},"404":{"description":"App not found, or the app has no entity with this name."},"429":{"description":"Rate limit exceeded. This endpoint allows 100 requests per minute per app, shared with the app's other entity listing calls."}}}},"/api/apps/{app_id}/entities/{entity_name}/{entity_id}":{"get":{"summary":"Get entity record","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns one record from one of the app's entities.\n\nRow-level security applies to the read, so a record the caller's `rls` read rule doesn't cover reports 404, the same as a record that was never there or has been deleted.\n\nThe response carries the entity's own fields alongside the ones Base44 adds. This endpoint has no `fields` parameter, so it always returns the whole record. Use [List entity records](/api-reference/list-entity-records) when you want only some of a record's fields.\n\nThis endpoint is limited to 150 requests per minute per app.\n\n<Note>This endpoint accepts a personal API key belonging to a user with access to the app, including a read-only key. Workspace API keys are not accepted.</Note>","operationId":"get_entity_api_apps__app_id__entities__entity_name___entity_id__get","parameters":[{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the record, as `id` in the response of [List entity records](/api-reference/list-entity-records).","title":"Entity Id"},"description":"ID of the record, as `id` in the response of [List entity records](/api-reference/list-entity-records).","example":"6886b8d390dc7e2f4a2c91b3"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app that owns the entity.","title":"App Id"},"description":"ID of the app that owns the entity.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"entity_name","in":"path","required":true,"schema":{"type":"string","description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it.","example":"Invoice"}],"responses":{"200":{"description":"The record.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"EntityRecord","description":"One record in one of an app's entities.","properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ID of the record. Pass it as `entity_id` to [Get entity record](/api-reference/get-entity-record), [Update entity record](/api-reference/update-entity-record) or [Delete entity record](/api-reference/delete-entity-record).","example":"6886b8d390dc7e2f4a2c91b3","title":"Id"},"created_date":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the record was created, as a UTC timestamp in ISO 8601 format. A record Base44 has just created carries a `Z` suffix, and a record read back from storage does not.","example":"2026-06-01T09:23:41.481000","title":"Created Date"},"updated_date":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the record last changed, as a UTC timestamp in ISO 8601 format. A record Base44 has just created carries a `Z` suffix, and a record read back from storage does not.","example":"2026-06-04T14:07:02.115000","title":"Updated Date"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Email of the app user who created the record, or `anonymous` when a visitor created it on an app that needs no login. Apps that hide record authorship leave this field out of the response.","example":"jane@acme.com","title":"Created By"},"created_by_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ID of the app user who created the record, or `anonymous` when a visitor created it on an app that needs no login.","example":"6874b0c2e1a94d0031bb77de","title":"Created By Id"},"is_sample":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether Base44 stored the record as sample data while the app was being built. A record you create reports `false`.","example":false,"title":"Is Sample"}}}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App not found, the app has no entity with this name, or the entity holds no record with this ID that you can read."},"429":{"description":"Rate limit exceeded. This endpoint allows 150 requests per minute per app."}}},"put":{"summary":"Update entity record","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nChanges the fields you send on one record and returns the whole updated record.\n\nThis merges rather than replaces. A field you leave out keeps the value it already has, so you can send a single field on its own. Base44 stores only the fields the entity's schema declares and drops the rest, so a misspelled name is left out of the record instead of failing the call. Base44 also assigns `id`, `created_date`, `updated_date`, `created_by` and `created_by_id`, and ignores them if you send them.\n\nRow-level security decides this differently from a read. A record the entity's `rls` update rule doesn't cover reports 403 even when the caller can't read it, so a 403 here tells you the record exists. Field-level rules can refuse a change to one field while allowing the rest.\n\nUpdating a record triggers the app's webhooks and any automation or workflow that listens for this entity.\n\nThis endpoint is limited to 100 requests per minute per app.\n\n<Note>This endpoint accepts a personal API key belonging to a user with access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"update_entity_api_apps__app_id__entities__entity_name___entity_id__put","parameters":[{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the record, as `id` in the response of [List entity records](/api-reference/list-entity-records).","title":"Entity Id"},"description":"ID of the record, as `id` in the response of [List entity records](/api-reference/list-entity-records).","example":"6886b8d390dc7e2f4a2c91b3"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app that owns the entity.","title":"App Id"},"description":"ID of the app that owns the entity.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"entity_name","in":"path","required":true,"schema":{"type":"string","description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it.","example":"Invoice"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"UpdateEntityRecord","description":"The record's fields, as a flat JSON object using the names the entity's [schema](/api-reference/get-entity-schema) declares."},"example":{"amount":4200,"status":"draft","customer_email":"jane@acme.com"}}}},"responses":{"200":{"description":"The updated record.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"EntityRecord","description":"One record in one of an app's entities.","properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ID of the record. Pass it as `entity_id` to [Get entity record](/api-reference/get-entity-record), [Update entity record](/api-reference/update-entity-record) or [Delete entity record](/api-reference/delete-entity-record).","example":"6886b8d390dc7e2f4a2c91b3","title":"Id"},"created_date":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the record was created, as a UTC timestamp in ISO 8601 format. A record Base44 has just created carries a `Z` suffix, and a record read back from storage does not.","example":"2026-06-01T09:23:41.481000","title":"Created Date"},"updated_date":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the record last changed, as a UTC timestamp in ISO 8601 format. A record Base44 has just created carries a `Z` suffix, and a record read back from storage does not.","example":"2026-06-04T14:07:02.115000","title":"Updated Date"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Email of the app user who created the record, or `anonymous` when a visitor created it on an app that needs no login. Apps that hide record authorship leave this field out of the response.","example":"jane@acme.com","title":"Created By"},"created_by_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ID of the app user who created the record, or `anonymous` when a visitor created it on an app that needs no login.","example":"6874b0c2e1a94d0031bb77de","title":"Created By Id"},"is_sample":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether Base44 stored the record as sample data while the app was being built. A record you create reports `false`.","example":false,"title":"Is Sample"}}}}}},"400":{"description":"A field value is longer than 20,000 characters."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the entity's `rls` update rule doesn't cover the record or one of the fields you're changing, or your API key is read-only."},"404":{"description":"App not found, the app has no entity with this name, or the entity holds no record with this ID."},"422":{"description":"The body is missing, or a value doesn't match the type the entity's schema declares for that field."},"429":{"description":"Rate limit exceeded. This endpoint allows 100 requests per minute per app."}}},"delete":{"summary":"Delete entity record","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nDeletes one record from one of the app's entities.\n\nThe record stops appearing in [List entity records](/api-reference/list-entity-records) and [Get entity record](/api-reference/get-entity-record) straight away. Base44 keeps it in the app's trash rather than erasing it, so an editor can restore it from the app's data screen.\n\nRow-level security applies to the lookup, so a record the entity's `rls` delete rule doesn't cover reports 404 rather than 403.\n\nDeleting a record triggers the app's webhooks and any automation or workflow that listens for this entity.\n\nThis endpoint is limited to 100 requests every 30 seconds per app.\n\n<Note>This endpoint accepts a personal API key belonging to a user with access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"delete_entity_api_apps__app_id__entities__entity_name___entity_id__delete","parameters":[{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the record, as `id` in the response of [List entity records](/api-reference/list-entity-records).","title":"Entity Id"},"description":"ID of the record, as `id` in the response of [List entity records](/api-reference/list-entity-records).","example":"6886b8d390dc7e2f4a2c91b3"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app that owns the entity.","title":"App Id"},"description":"ID of the app that owns the entity.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"entity_name","in":"path","required":true,"schema":{"type":"string","description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it.","example":"Invoice"}],"responses":{"200":{"description":"The record was deleted.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"DeleteEntityRecordResponse","description":"Confirmation that a record was deleted.","properties":{"success":{"description":"Always `true`. A delete that doesn't happen returns an error instead.","example":true,"title":"Success","type":"boolean"}},"required":["success"]}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or your API key is read-only."},"404":{"description":"App not found, the app has no entity with this name, or the entity holds no record with this ID that the `rls` delete rule lets you remove."},"429":{"description":"Rate limit exceeded. This endpoint allows 100 requests every 30 seconds per app."}}}},"/api/apps/{app_id}/google-ads/accounts":{"get":{"summary":"Get Google Ads account","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the app's Google Ads account: its status, currency, billing cadence, spend cap and the Google Business Profile linked to it.\n\nAn app has at most one Google Ads account, and **returns `null` with a `200` when it has none** — that is the normal state for an app that has never advertised, not an error. `status` is the field to read before anything else, because only an `ACTIVE` account can create or resume campaigns. `budget_active` is the second. A campaign cannot serve without a live Google Ads budget behind it.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"get_account_api_apps__app_id__google_ads_accounts_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/GoogleAdsAccountResource"},{"type":"null"}],"title":"Response 200 Get Account Api Apps  App Id  Google Ads Accounts Get"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."}}}},"/api/apps/{app_id}/google-ads/accounts/{account_id}/accept-terms":{"post":{"summary":"Accept Google Ads terms","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRecords acceptance of the Google Ads terms for the account.\n\nThe account cannot be provisioned until the terms are accepted.\n\n<Warning>A `not_recorded` status is a failure, not a no-op. It means the account was in a blocked or terminal state (`BLOCKED`, `SUSPENDED`, `BILLING_HOLD`, `CANCELED` or `PENDING_VERIFICATION`) and the acceptance was not written. Read the account's `status` and resolve that before retrying. The response is still a 200.</Warning>\n\nThis needs a payment method on the workspace, and the workspace must not be on a billing hold.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"accept_terms_api_apps__app_id__google_ads_accounts__account_id__accept_terms_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to advertise.","title":"App Id"},"description":"ID of the app to advertise.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"account_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account that belongs to a different app returns a 404.","title":"Account Id"},"description":"ID of the Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account that belongs to a different app returns a 404.","example":"68b1c0d4e7b91d003c45a1f8"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptTermsResponse"}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"No payment method on the workspace."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or no account with this ID belongs to this app."},"409":{"description":"The workspace is on a billing hold."}}}},"/api/apps/{app_id}/google-ads/accounts/{account_id}/retry-billing-setup":{"post":{"summary":"Retry Google Ads billing setup","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRetries the Google Ads billing setup for an account whose first attempt failed.\n\nUse this when the account is stuck in `PENDING_BILLING_SETUP`. It returns the account, so read `status` on the response to see whether the retry moved it on. A retry that fails again leaves the account where it was rather than erroring.\n\nThis needs a payment method on the workspace, and the workspace must not be on a billing hold.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"retry_billing_setup_api_apps__app_id__google_ads_accounts__account_id__retry_billing_setup_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to advertise.","title":"App Id"},"description":"ID of the app to advertise.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"account_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account that belongs to a different app returns a 404.","title":"Account Id"},"description":"ID of the Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account that belongs to a different app returns a 404.","example":"68b1c0d4e7b91d003c45a1f8"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsAccountResource"}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"No payment method on the workspace."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or no account with this ID belongs to this app."},"409":{"description":"The workspace is on a billing hold."}}}},"/api/apps/{app_id}/google-ads/accounts/{account_id}/disconnect":{"post":{"summary":"Disconnect the Google Ads account","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nDisconnects the app's Google Ads account.\n\nThis is the end of the account's life in Base44. Campaigns stop serving and the account's Google Ads budget is wound down.\n\n<Warning>Outstanding ad spend is still owed. Disconnecting does not cancel a balance, and Base44 continues to settle invoices for spend that already happened.</Warning>\n\nRead `budget_end_confirmed` on the response. A value of `false` means the disconnect is recorded but Google has not yet confirmed the budget was ended, so the wind-down is still in progress.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"disconnect_account_api_apps__app_id__google_ads_accounts__account_id__disconnect_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"account_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account that belongs to a different app returns a 404.","title":"Account Id"},"description":"ID of the Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account that belongs to a different app returns a 404.","example":"68b1c0d4e7b91d003c45a1f8"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisconnectAccountResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or no account with this ID belongs to this app."}}}},"/api/apps/{app_id}/google-ads/billing/workspace-debt":{"get":{"summary":"Get Google Ads workspace debt","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns what the workspace owes for Google Ads spend, and whether it can be settled.\n\nThe balance is workspace-wide rather than per app. Base44 holds Google Ads at the workspace level, so this resolves the held account across every app in the workspace and reports it from whichever app you ask through. That is why `app_id` in the response can name a different app from the one in the path. Read `state` first, since `no_workspace_debt` is the normal answer and leaves every other field at a neutral default rather than omitting it.\n\nNothing here charges anything. Settling a balance is a card transaction Base44 deliberately keeps out of this API, so treat a `payable` state as something to raise with whoever owns the card.\n\n<Note>Unlike the rest of the Google Ads API, this endpoint stays available while Google Ads is switched off for you, so a workspace can always see what it owes.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"get_workspace_debt_recovery_summary_api_apps__app_id__google_ads_billing_workspace_debt_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"What the workspace owes, and whether it can be settled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceDebtSummary"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."}}}},"/api/apps/{app_id}/google-ads/billing/status":{"get":{"summary":"Get Google Ads billing status","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReports whether the workspace can be charged for ad spend, and shows the card on file.\n\nRead this before creating or resuming a campaign, both of which need a usable card.\n\n<Warning>The check fails closed. When Base44 cannot reach the payment provider it returns `has_payment_method: false` with `stripe_unavailable: true`, which means \"could not check\" rather than \"no card\". Retry in that case instead of telling someone to add a card.</Warning>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"get_billing_status_api_apps__app_id__google_ads_billing_status_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingStatusResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."}}}},"/api/apps/{app_id}/google-ads/accounts/{account_id}/cadence":{"patch":{"summary":"Update Google Ads billing cadence","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nChanges how often the account is charged for ad spend.\n\nSend `1w` for weekly, `2w` for fortnightly, or `1m` for monthly.\n\n<Warning>This only changes when charges happen on an account whose `charge_trigger_mode` is `cadence`. On a legacy `threshold` account the value is stored and the call still returns a 200, but charging keeps following the spend ladder and ignores it. Read `charge_trigger_mode` from [Get Google Ads account](/api-reference/get-google-ads-account) before relying on this.</Warning>\n\n<Warning>On a `cadence` account the new setting applies to spend that has already accrued, not only to spend from here on. The next charge is calculated from the account's existing creation or last-charge timestamp using the cadence you just set, so shortening it can bring forward a charge for spend accrued under the old one, and lengthening it can delay that charge.</Warning>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"update_billing_cadence_api_apps__app_id__google_ads_accounts__account_id__cadence_patch","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"account_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account that belongs to a different app returns a 404.","title":"Account Id"},"description":"ID of the Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account that belongs to a different app returns a 404.","example":"68b1c0d4e7b91d003c45a1f8"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"UpdateBillingCadence","type":"object","required":["billing_cadence"],"properties":{"billing_cadence":{"type":"string","enum":["1w","2w","1m"],"description":"How often to charge the account: weekly, fortnightly, or monthly."}}},"example":{"billing_cadence":"1w"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingCadenceResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or no account with this ID belongs to this app."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/accounts/{account_id}/budget-limit":{"patch":{"summary":"Update Google Ads monthly spend cap","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSets the account's monthly spend cap.\n\nSend `monthly_limit_dollars` in whole units of the account currency, not micros. It must be above zero and Base44 enforces an upper bound, so an unreasonably large value is rejected with a 422.\n\nRaising the cap opens more spend, so it needs a chargeable account and a workspace that is not on a billing hold. Base44 pushes the new cap to Google Ads, so Google can reject it and that comes back as a 400 with its reason.\n\n<Warning>A 409 means the change may or may not have been applied. The request to Google Ads timed out after it was sent. Read the campaign back with [Get campaign](/api-reference/get-google-ads-campaign) before retrying, or you can end up applying it twice.</Warning>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"update_budget_limit_api_apps__app_id__google_ads_accounts__account_id__budget_limit_patch","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"account_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account that belongs to a different app returns a 404.","title":"Account Id"},"description":"ID of the Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account that belongs to a different app returns a 404.","example":"68b1c0d4e7b91d003c45a1f8"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"UpdateMonthlySpendCap","type":"object","required":["monthly_limit_dollars"],"properties":{"monthly_limit_dollars":{"type":"integer","minimum":1,"description":"Monthly spend cap in whole units of the account currency, not micros. Must be above zero, and Base44 enforces an upper bound."}}},"example":{"monthly_limit_dollars":500}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BudgetLimitResponse"}}}},"400":{"description":"Google Ads rejected the change. The response message carries Google's reason."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or no account with this ID belongs to this app."},"409":{"description":"The account cannot be charged, or the workspace is on a billing hold. The request to Google Ads timing out after being sent also reports 409."},"429":{"description":"Google Ads is rate limiting the account. Retry later."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/launch-readiness":{"get":{"summary":"Get Google Ads launch readiness","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns what Base44 will do on launch, so you can check a launch will succeed before spending anything.\n\nRead this first. It tells you the currency the account will be billed in, whether a card is on file, the business details that will go on the Google Ads account, and the landing page Google will be asked to crawl. Send `landing_page` back verbatim on [Launch campaign](/api-reference/launch-google-ads-campaign) rather than deriving the URL yourself: Base44 prefers a custom domain only once it actually routes, and a URL Google cannot fetch gets the campaign rejected after the account already exists.\n\nNothing is created or charged by this call.\n\n`landing_page_ready` is best-effort. If Base44 cannot check the app's published address it comes back `false` with an empty `landing_page`, which is indistinguishable from an app that genuinely has no crawlable page.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"get_launch_readiness_api_apps__app_id__google_ads_launch_readiness_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to advertise.","title":"App Id"},"description":"ID of the app to advertise.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"What a launch would do, and whether it can succeed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchReadinessResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."}}}},"/api/apps/{app_id}/google-ads/launch":{"post":{"summary":"Launch Google Ads campaign","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nCreates the app's Google Ads account if it does not have one, then creates a campaign on it. This is what starts spending money.\n\nCall [Get launch readiness](/api-reference/get-google-ads-launch-readiness) first and send its `landing_page` back here. Its `currency_code` is informational: there is no currency field on this request, and Base44 derives the account's currency itself.\n\n<Warning>Creating the Google Ads account cannot be undone. There is no endpoint that deletes one, and an account keeps its currency and time zone for life. A later launch reuses that account, so the first attempt is what commits you to its currency.</Warning>\n\n<Warning>A failure at the campaign step is not automatically safe to retry. Google Ads can create the campaign and then a follow-up step fail, which leaves it live and spending while Base44 has no record of it — so it does not appear in [List campaigns](/api-reference/list-google-ads-campaigns) either. The error message says when that is what happened and tells you not to re-approve. Read it before retrying.</Warning>\n\nBase44 checks what it can from the request before creating anything: a Performance Max campaign needs `logo_url`, the budget has to sit inside the per-currency range for the campaign type, the business name has a length limit, and targeting that can reach the EU needs an EU political-advertising declaration. Those all fail with a 400 and create nothing. Anything only Google can judge — a logo it cannot fetch, a landing page it cannot crawl — fails after the account exists.\n\nA 409 has four different causes and they are not all safe to retry:\n\n- the workspace is on billing hold, or the account is, so no new spend is allowed. Fix the billing problem and retry.\n- the campaign was already launched from this draft. Retrying will not create a second one.\n- **the request to Google Ads timed out after being sent.** The campaign may be live and spending. Do not retry: read [List campaigns](/api-reference/list-google-ads-campaigns) and check before doing anything else.\n\nThe response carries the account and the campaign. A campaign can come back paused when its conversion tracking is not wired yet; Base44 enables it on its own once tracking verifies.\n\nLaunching is refused, rather than allowed through, while Base44 cannot reach its payment provider to confirm a card is on file. [Get launch readiness](/api-reference/get-google-ads-launch-readiness) reports that state as `stripe_unavailable`, so check it there instead of inferring it from a failed launch.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"launch_campaign_api_apps__app_id__google_ads_launch_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to advertise.","title":"App Id"},"description":"ID of the app to advertise.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"LaunchCampaign","type":"object","required":["campaign"],"properties":{"campaign":{"title":"LaunchCampaignDetails","type":"object","required":["campaign_name","daily_budget_micros"],"properties":{"campaign_name":{"type":"string","description":"Name for the campaign."},"campaign_type":{"type":"string","enum":["SMART","PERFORMANCE_MAX"],"default":"SMART","description":"Which campaign type to create."},"daily_budget_micros":{"type":"integer","description":"Daily budget in micros of the account currency, so `15000000` is 15.00. Google enforces a per-currency minimum and rejects anything below it with a 400."},"landing_page":{"type":"string","description":"URL the ads send clicks to. Google rejects the campaign if it cannot reach this page."},"logo_url":{"type":"string","description":"URL of a square logo image. Required for a `PERFORMANCE_MAX` campaign and rejected with a 400 when missing; ignored for `SMART`. Google must be able to fetch it."},"keyword_themes":{"type":"array","items":{"type":"string"},"description":"Themes to match searches on, for a `SMART` campaign."},"headlines":{"type":"array","items":{"type":"string"},"description":"Ad headlines. Google reviews these against its advertising policies."},"descriptions":{"type":"array","items":{"type":"string"},"description":"Ad description lines. Google reviews these against its advertising policies."},"geo_targets":{"type":"array","items":{"type":"string"},"description":"Google Ads geo target constant IDs to target."},"business_name":{"type":"string","description":"Business name shown in the ad."},"phone_number":{"type":"string","description":"Phone number shown in the ad. On PERFORMANCE_MAX it becomes a call asset and must be in international format with the country code (e.g. +18506168085) — Google needs the country alongside the number, and any other format is rejected with a 400. SMART campaigns take it as typed. Omit for no phone."},"contains_eu_political_advertising":{"type":"boolean","description":"Whether the campaign carries political advertising. Required before an EU advertiser can create a campaign."},"settings":{"type":"object","description":"Campaign settings. `language_code` is the only key this API commits to; anything else is passed through undocumented.","properties":{"language_code":{"type":"string","description":"Two-letter code for the language the ad copy is written in, which becomes the campaign's advertising language. English when absent or unsupported — `Accept-Language` does not affect it. Pass through `settings` from [Suggest Google Ads campaigns](/api-reference/suggest-google-ads-campaigns) to keep a generated campaign's targeting on its copy's language."}}}},"description":"The campaign to create. Same fields as [Create campaign](/api-reference/create-google-ads-campaign)."},"timezone":{"type":"string","description":"Time zone to create the Google Ads account in, as an IANA name. Only used when the account does not exist yet, and fixed for its lifetime after that. Defaults to `UTC`."}}},"example":{"campaign":{"campaign_name":"Spring sale","campaign_type":"SMART","daily_budget_micros":15000000,"landing_page":"https://example.com","keyword_themes":["oak furniture","dining table"],"business_name":"Nordwind Furniture"},"timezone":"Europe/Berlin"}}}},"responses":{"200":{"description":"The account the campaign lives on, and the campaign.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchResponse"}}}},"400":{"description":"The request cannot produce a campaign: a Performance Max campaign with no `logo_url`, a budget outside the allowed range for the currency and campaign type, a business name that is too long, EU-reaching targeting with no political-advertising declaration, or a rejection passed through from Google Ads."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The workspace has no payment method on file."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The `draft_id` does not name a launch draft for this app."},"409":{"description":"One of four things: the workspace is on billing hold, the account is, this draft was already launched, or the request to Google Ads timed out after being sent and the campaign may be live. The message says which; only the last one must not be retried."},"429":{"description":"Google Ads is rate limiting the account. Retry later."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/campaigns":{"post":{"summary":"Create Google Ads campaign","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nCreates a campaign on the app's Google Ads account and starts serving it.\n\nThis spends money. The workspace needs a payment method on file and an account in good standing. Without either, the call returns a 402 and no campaign is created. A campaign is also created paused when the app's conversion tracking is not yet verified, so check `status` on the response rather than assuming it is serving.\n\nSet `campaign_type` to `SMART` or `PERFORMANCE_MAX`. `daily_budget_micros` is in micros of the account currency and has a per-currency floor Google enforces. A budget below it comes back as a 400 carrying Google's reason, and the campaign is not created. Advertisers in the EU must declare whether the campaign carries political advertising. The call is rejected until that declaration is on file.\n\nGoogle validates the campaign as it is created, so a rejected ad text, an unreachable landing page, or a budget below the floor comes back as a 400 with Google's own reason.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>A 409 means the change may or may not have been applied. The request to Google Ads timed out after it was sent. Read the campaign back with [Get campaign](/api-reference/get-google-ads-campaign) before retrying, or you can end up applying it twice.</Warning>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"create_campaign_api_apps__app_id__google_ads_campaigns_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to advertise.","title":"App Id"},"description":"ID of the app to advertise.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"CreateCampaign","type":"object","required":["campaign_name","daily_budget_micros"],"properties":{"campaign_name":{"type":"string","description":"Name for the campaign."},"campaign_type":{"type":"string","enum":["SMART","PERFORMANCE_MAX"],"default":"SMART","description":"Which campaign type to create."},"daily_budget_micros":{"type":"integer","description":"Daily budget in micros of the account currency, so `15000000` is 15.00. Google enforces a per-currency minimum and rejects anything below it with a 400."},"landing_page":{"type":"string","description":"URL the ads send clicks to. Google rejects the campaign if it cannot reach this page."},"logo_url":{"type":"string","description":"URL of a square logo image. Required for a `PERFORMANCE_MAX` campaign and rejected with a 400 when missing; ignored for `SMART`. Google must be able to fetch it."},"keyword_themes":{"type":"array","items":{"type":"string"},"description":"Themes to match searches on, for a `SMART` campaign."},"headlines":{"type":"array","items":{"type":"string"},"description":"Ad headlines. Google reviews these against its advertising policies."},"descriptions":{"type":"array","items":{"type":"string"},"description":"Ad description lines. Google reviews these against its advertising policies."},"geo_targets":{"type":"array","items":{"type":"string"},"description":"Google Ads geo target constant IDs to target."},"business_name":{"type":"string","description":"Business name shown in the ad."},"phone_number":{"type":"string","description":"Phone number shown in the ad. On PERFORMANCE_MAX it becomes a call asset and must be in international format with the country code (e.g. +18506168085) — Google needs the country alongside the number, and any other format is rejected with a 400. SMART campaigns take it as typed. Omit for no phone."},"contains_eu_political_advertising":{"type":"boolean","description":"Whether the campaign carries political advertising. Required before an EU advertiser can create a campaign."},"settings":{"type":"object","description":"Campaign settings. `language_code` is the only key this API commits to; anything else is passed through undocumented.","properties":{"language_code":{"type":"string","description":"Two-letter code for the language the ad copy is written in, which becomes the campaign's advertising language. English when absent or unsupported — `Accept-Language` does not affect it. Pass through `settings` from [Suggest Google Ads campaigns](/api-reference/suggest-google-ads-campaigns) to keep a generated campaign's targeting on its copy's language."}}}}},"example":{"campaign_name":"Spring sale in Berlin","campaign_type":"SMART","daily_budget_micros":15000000,"landing_page":"https://example.com/spring","keyword_themes":["spring sale","discount furniture"],"geo_targets":["1003854"]}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResource"}}}},"400":{"description":"Google Ads rejected the request, for example ad text that breaks its policies or a budget below the currency's minimum. The response message carries Google's reason."},"409":{"description":"The request to Google Ads timed out after being sent, so the change may or may not have been applied. Read the campaign back before retrying."},"429":{"description":"Google Ads is rate limiting the account. Retry later."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"No payment method on file, or the workspace has an unpaid balance."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or there is no campaign with this ID."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Google Ads campaigns","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the app's Google Ads campaigns, including paused and deleted ones. Check `status` to tell them apart.\n\nEach row carries the campaign's `id`, name, type, status, daily budget, and its performance metrics. Pass an `id` to [Get campaign](/api-reference/get-google-ads-campaign) for the campaign's landing page, targeting, and phone number, which the list rows leave out, or to the pause, resume, update, and delete endpoints.\n\nBy default Base44 reads the metrics live from Google Ads. Pass `include_metrics=false` to skip that round trip, which returns the same fields with the metric ones zeroed and `metrics_pending` set to `true`. Either way `primary_status` comes back empty on this endpoint. Read one campaign to get it.\n\n<Warning>The live read returns at most 50 campaigns, ordered by spend, and there is no pagination. An account with more than 50 active campaigns gets an incomplete list with nothing in the response to say so.</Warning>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"list_campaigns_api_apps__app_id__google_ads_campaigns_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"include_metrics","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to include each campaign's performance metrics, which are fetched from Google on the fly. Pass `false` to return the cached campaign records on their own, which is faster.","default":true,"title":"Include Metrics"},"description":"Whether to include each campaign's performance metrics, which are fetched from Google on the fly. Pass `false` to return the cached campaign records on their own, which is faster."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CampaignListItem"},"title":"Response 200 List Campaigns Api Apps  App Id  Google Ads Campaigns Get"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/campaigns/metrics":{"get":{"summary":"Get Google Ads campaign metrics","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReads live performance figures for every campaign on the account over a date window, in one call.\n\nThis goes to Google directly rather than to Base44's nightly copy, so the numbers include today. Use it to fill in a table you first drew from [List campaigns](/api-reference/list-google-ads-campaigns) with `include_metrics=false`.\n\n`metrics` is keyed by the Google Ads campaign ID, not Base44's campaign ID. Join on the `google_campaign_id` field from the list endpoint. A campaign that had no activity in the window does not appear as a zero row, it is simply absent.\n\nWhen Google cannot be reached you get an empty `metrics` map with `degraded` set to `true`, under a 200. Treat that as \"unknown\" rather than \"nothing happened\".\n\n<Note>`start_date` and `end_date` are inclusive and must both be `YYYY-MM-DD`. Anything else is rejected with a 400.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"campaign_metrics_api_apps__app_id__google_ads_campaigns_metrics_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads reporting you want to read.","title":"App Id"},"description":"ID of the app whose Google Ads reporting you want to read.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"start_date","in":"query","required":true,"schema":{"type":"string","description":"First day to include, as `YYYY-MM-DD`. Inclusive.","title":"Start Date"},"description":"First day to include, as `YYYY-MM-DD`. Inclusive.","example":"2026-08-01"},{"name":"end_date","in":"query","required":true,"schema":{"type":"string","description":"Last day to include, as `YYYY-MM-DD`. Inclusive.","title":"End Date"},"description":"Last day to include, as `YYYY-MM-DD`. Inclusive.","example":"2026-08-31"}],"responses":{"200":{"description":"Metrics for each campaign with activity in the window.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsCampaignMetricsResponse"}}}},"400":{"description":"`start_date` or `end_date` is not `YYYY-MM-DD`, or `start_date` is after `end_date`."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/campaigns/budget-boundaries":{"get":{"summary":"Get Google Ads budget boundaries","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReads the daily budget range Google accepts for a campaign type and currency, so you can validate a budget before you create a campaign.\n\nThe numbers come from a fixed table rather than from Google, because Google does not publish them as a queryable resource. They change rarely. `min_daily_budget_micros` is the same floor the create, update, and bulk-budget endpoints enforce, so a budget that passes here is accepted there.\n\nYou do not need a connected Google Ads account to call this, which is what lets you show a budget slider before the account exists. An unrecognized `currency_code` or `campaign_type` is not rejected: you get the US dollar and Smart campaign defaults back, with the values you sent echoed in `currency_code` and `campaign_type`. Compare those two fields against what you sent if you need to know that happened.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"get_campaign_budget_boundaries_api_apps__app_id__google_ads_campaigns_budget_boundaries_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app you are planning a Google Ads campaign for.","title":"App Id"},"description":"ID of the app you are planning a Google Ads campaign for.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"currency_code","in":"query","required":false,"schema":{"type":"string","description":"Currency to report the bounds in, as an ISO 4217 code. An unrecognized code falls back to `USD`.","default":"USD","title":"Currency Code"},"description":"Currency to report the bounds in, as an ISO 4217 code. An unrecognized code falls back to `USD`.","example":"USD"},{"name":"campaign_type","in":"query","required":false,"schema":{"type":"string","description":"Campaign type to report bounds for: `SMART` or `PERFORMANCE_MAX`. Anything else falls back to `SMART`.","default":"SMART","title":"Campaign Type"},"description":"Campaign type to report bounds for: `SMART` or `PERFORMANCE_MAX`. Anything else falls back to `SMART`.","example":"SMART"}],"responses":{"200":{"description":"The accepted daily budget range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsBudgetBoundaries"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."}}}},"/api/apps/{app_id}/google-ads/campaigns/{campaign_id}/change-log":{"get":{"summary":"List Google Ads campaign changes","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns a campaign's change history, newest first: every pause, resume, edit, and deletion, with who made it and when.\n\n`event_type` says what happened. An `EDITED` entry carries the field and its before and after values in `details`. `actor` is `system` for a change Base44 made on its own, such as pausing a campaign over an unpaid balance, and otherwise identifies the user who made it.\n\nUse `limit` to cap how many entries come back. The default is 50 and the most you can ask for is 500. There is no paging, so a campaign with a long history returns only its most recent entries.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"get_campaign_change_log_api_apps__app_id__google_ads_campaigns__campaign_id__change_log_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","title":"Campaign Id"},"description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","example":"68b1c0d4e7b91d003c45a1f2"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Most entries to return, newest first.","default":50,"title":"Limit"},"description":"Most entries to return, newest first."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CampaignChangeLogEntry"},"title":"Response 200 Get Campaign Change Log Api Apps  App Id  Google Ads Campaigns  Campaign Id  Change Log Get"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or there is no campaign with this ID."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/campaigns/{campaign_id}":{"get":{"summary":"Get Google Ads campaign","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns one campaign, with the status fields Google reports for it.\n\nUse this for a single campaign's current state. It reads the same cache as [List campaigns](/api-reference/list-google-ads-campaigns) and additionally folds in a live check of whether Google has disapproved the campaign's assets, so a rejected Performance Max campaign shows up here.\n\n<Note>A campaign's name, budget, and targeting come from Base44's own record, which is refreshed in the background, so a change made directly in the Google Ads UI can take a few minutes to appear here.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"get_campaign_api_apps__app_id__google_ads_campaigns__campaign_id__get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","title":"Campaign Id"},"description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","example":"68b1c0d4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResource"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or there is no campaign with this ID."}}},"delete":{"summary":"Delete Google Ads campaign","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nDeletes a campaign and stops it serving.\n\nThis is permanent. There is no undo, and the campaign cannot be restored through this API. To stop a campaign temporarily, use [Pause campaign](/api-reference/pause-google-ads-campaign) instead.\n\nGoogle keeps a deleted campaign for reporting, so it stays in [List campaigns](/api-reference/list-google-ads-campaigns) with `status` of `REMOVED` and its past performance still counts toward account totals.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>A 409 means the change may or may not have been applied. The request to Google Ads timed out after it was sent. Read the campaign back with [Get campaign](/api-reference/get-google-ads-campaign) before retrying, or you can end up applying it twice.</Warning>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"delete_campaign_api_apps__app_id__google_ads_campaigns__campaign_id__delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","title":"Campaign Id"},"description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","example":"68b1c0d4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignStatusResponse"}}}},"400":{"description":"Google Ads rejected the request, for example ad text that breaks its policies or a budget below the currency's minimum. The response message carries Google's reason."},"409":{"description":"The request to Google Ads timed out after being sent, so the change may or may not have been applied. Read the campaign back before retrying."},"429":{"description":"Google Ads is rate limiting the account. Retry later."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or there is no campaign with this ID."}}},"patch":{"summary":"Update Google Ads campaign","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nChanges one section of a campaign and pushes it to Google.\n\nSend the section in `field` and its new value in `value`. One section per request. To change a name and a budget, send two requests.\n\nChanging `ad_copy` sends the new text back through Google's policy review, so the campaign can return to a reviewing state and stop serving until it clears. Changing `budget` takes effect for the rest of the current day.\n\nGoogle validates the change, so text that breaks its policies or a budget below the currency's floor comes back as a 400 with Google's reason and the campaign is left as it was. To change the budget on several campaigns at once, use [Update campaign budgets in bulk](/api-reference/bulk-update-google-ads-campaign-budgets).\n\n<Note>The response carries `id` plus only the section you changed, not the whole campaign. Read the campaign back with [Get campaign](/api-reference/get-google-ads-campaign) when you need its full state.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>A 409 means the change may or may not have been applied. The request to Google Ads timed out after it was sent. Read the campaign back with [Get campaign](/api-reference/get-google-ads-campaign) before retrying, or you can end up applying it twice.</Warning>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"update_campaign_api_apps__app_id__google_ads_campaigns__campaign_id__patch","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","title":"Campaign Id"},"description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","example":"68b1c0d4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"UpdateCampaignRequest","type":"object","required":["field","value"],"properties":{"field":{"type":"string","enum":["name","budget","details","ad_copy","targeting","images","asset"],"description":"Which part of the campaign to change. One section per request."},"value":{"description":"The new value, shaped by `field`. A `name` takes a string, `budget` takes the daily budget in micros as a number, and `ad_copy` takes an object with any of `headlines`, `descriptions`, `long_headlines`, and `business_name`. The `targeting`, `details`, `images`, and `asset` fields take an object whose shape follows that section of the campaign."}}},"examples":{"rename":{"summary":"Rename the campaign","value":{"field":"name","value":"Spring sale in Berlin"}},"budget":{"summary":"Raise the daily budget to 15.00","value":{"field":"budget","value":15000000}},"ad_copy":{"summary":"Replace the headlines","value":{"field":"ad_copy","value":{"headlines":["Spring sale","Up to 40% off"]}}}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignPatchResult"}}}},"402":{"description":"Google Ads reported a billing problem on the account."},"400":{"description":"Google Ads rejected the request, for example ad text that breaks its policies or a budget below the currency's minimum. The response message carries Google's reason."},"409":{"description":"The request to Google Ads timed out after being sent, so the change may or may not have been applied. Read the campaign back before retrying."},"429":{"description":"Google Ads is rate limiting the account. Retry later."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or there is no campaign with this ID."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/campaigns/bulk-budget":{"patch":{"summary":"Bulk update Google Ads campaign budgets","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nChanges the daily budget on several campaigns in one call.\n\nSend one entry per campaign in `updates`, each with a `campaign_id` and a `daily_budget_micros`. Budgets are absolute, not deltas, and are in micros of the account currency.\n\nEach entry is applied on its own and this is **not** atomic. The response lists the campaigns that were updated in `succeeded` and the ones that were not in `failed`, each with a reason. A partially applied call still returns a 200, so check `failed` rather than relying on the status code. Re-sending an entry is safe, because the budget is set to the value you send rather than added to it.\n\n<Warning>An entry in `failed` usually means the budget did not change, but it can also mean Google accepted the new budget and only Base44's own record of it failed to save. In that case the campaign is already spending at the new budget. Read the campaign back with [Get campaign](/api-reference/get-google-ads-campaign) before you treat a failed entry as unchanged.</Warning>\n\nEach `reason` is free text meant for a person to read. Do not branch on it.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"bulk_update_campaigns_budget_api_apps__app_id__google_ads_campaigns_bulk_budget_patch","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"BulkUpdateCampaignBudgets","type":"object","required":["updates"],"properties":{"updates":{"type":"array","description":"One entry per campaign. An empty array is accepted and changes nothing.","items":{"type":"object","required":["campaign_id","daily_budget_micros"],"properties":{"campaign_id":{"type":"string","description":"Campaign to update, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns)."},"daily_budget_micros":{"type":"integer","description":"New daily budget in micros of the account currency. Absolute, not a delta."}}}}}},"example":{"updates":[{"campaign_id":"68b1c0d4e7b91d003c45a1f2","daily_budget_micros":15000000},{"campaign_id":"68b1c0d4e7b91d003c45a1f4","daily_budget_micros":8000000}]}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkBudgetUpdateResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/accounts/{account_id}/auto-renewal/on":{"post":{"summary":"Turn on Google Ads budget auto-renewal","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nTurns on automatic renewal of the account's Google Ads budget.\n\n<Warning>This sets a bookkeeping flag and nothing else. It does not create or extend a Google Ads budget, and it does not resume anything. Under Base44's consolidated billing there is no renewing subscription behind it.</Warning>\n\n`budget_window_end` on [Get Google Ads account](/api-reference/get-google-ads-account) reports the current window regardless of this flag.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"turn_on_auto_renewal_api_apps__app_id__google_ads_accounts__account_id__auto_renewal_on_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"account_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account that belongs to a different app returns a 404.","title":"Account Id"},"description":"ID of the Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account that belongs to a different app returns a 404.","example":"68b1c0d4e7b91d003c45a1f8"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoRenewalResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or no account with this ID belongs to this app."}}}},"/api/apps/{app_id}/google-ads/accounts/{account_id}/auto-renewal/off":{"post":{"summary":"Turn off Google Ads budget auto-renewal","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nTurns off automatic renewal of the account's Google Ads budget.\n\n<Warning>This sets a bookkeeping flag and does not stop spend. It does not end the budget and it does not pause campaigns, so ads keep serving and keep accruing charges after you call it. To actually stop spend, pause the campaigns with [Pause all Google Ads campaigns](/api-reference/pause-all-google-ads-campaigns) or lower the cap with [Update Google Ads monthly spend cap](/api-reference/update-google-ads-monthly-spend-cap).</Warning>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"turn_off_auto_renewal_api_apps__app_id__google_ads_accounts__account_id__auto_renewal_off_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"account_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account that belongs to a different app returns a 404.","title":"Account Id"},"description":"ID of the Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account that belongs to a different app returns a 404.","example":"68b1c0d4e7b91d003c45a1f8"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoRenewalResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or no account with this ID belongs to this app."}}}},"/api/apps/{app_id}/google-ads/geo-targets/search":{"get":{"summary":"Search Google Ads locations","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSearches Google's location catalog so you can offer a location picker when you create or edit a campaign.\n\nPass what the person is typing as `query`. Results come back ordered by how well they match, and each one carries the `resource_name` you pass to [Create campaign](/api-reference/create-google-ads-campaign) in `geo_targets`.\n\nYou do not need a connected Google Ads account, so this works before the account exists. Set `country_code` to keep results inside one country. An empty `query` returns Google's unfiltered suggestions rather than an error.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"search_geo_targets_api_apps__app_id__google_ads_geo_targets_search_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app you are planning a Google Ads campaign for.","title":"App Id"},"description":"ID of the app you are planning a Google Ads campaign for.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"query","in":"query","required":false,"schema":{"type":"string","description":"What the person is typing. Shorter than two characters is still accepted.","default":"","title":"Query"},"description":"What the person is typing. Shorter than two characters is still accepted.","example":"san fran"},{"name":"country_code","in":"query","required":false,"schema":{"type":"string","description":"Keep results inside one country, as an ISO 3166-1 alpha-2 code. Empty searches everywhere.","default":"","title":"Country Code"},"description":"Keep results inside one country, as an ISO 3166-1 alpha-2 code. Empty searches everywhere.","example":"US"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Most results to return.","default":20,"title":"Limit"},"description":"Most results to return.","example":20}],"responses":{"200":{"description":"Locations matching the search string.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GoogleAdsGeoTarget"},"title":"GoogleAdsGeoTargets"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/suggestions/keyword-themes":{"get":{"summary":"Autocomplete Google Ads keyword themes","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSuggests keyword themes as someone types, for the keyword picker on a Smart campaign.\n\nReturns matching theme names from Google's catalog, most relevant first, and never more than `limit`. Anything the caller types is still valid to save, so treat these as suggestions rather than the allowed set.\n\nA `query` shorter than two characters returns an empty list rather than an error, because a one-character prefix matches too much to be useful. `country_code` scopes the catalog and is required in practice: Google returns nothing without one, so leaving it unset uses `US`.\n\n`language_code` is best-effort. Google's catalog covers a fixed set of languages, and anything outside it falls back to English rather than failing, so a request for an unsupported language returns English themes with no signal that the substitution happened. Send a two-letter code; a regional tag like `pt-BR` is reduced to its first part.\n\nAn empty list also means Google was briefly unreachable, so treat \"no suggestions\" as \"nothing to show right now\" rather than \"this word has no themes\".\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"suggest_keyword_themes_autocomplete_api_apps__app_id__google_ads_suggestions_keyword_themes_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app you are planning a Google Ads campaign for.","title":"App Id"},"description":"ID of the app you are planning a Google Ads campaign for.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"query","in":"query","required":false,"schema":{"type":"string","description":"Free-text the user is typing","default":"","title":"Query"},"description":"Free-text the user is typing"},{"name":"language_code","in":"query","required":false,"schema":{"type":"string","description":"Two-letter language code","default":"en","title":"Language Code"},"description":"Two-letter language code"},{"name":"country_code","in":"query","required":false,"schema":{"type":"string","description":"Two-letter country code (scopes the catalog)","default":"US","title":"Country Code"},"description":"Two-letter country code (scopes the catalog)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":25,"minimum":1,"description":"Most suggestions to return.","default":10,"title":"Limit"},"description":"Most suggestions to return.","example":10}],"responses":{"200":{"description":"Matching keyword theme names, most relevant first.","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"title":"GoogleAdsKeywordThemeNames"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/campaigns/{campaign_id}/pause":{"post":{"summary":"Pause Google Ads campaign","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nStops a campaign serving. It keeps its settings and its history, and [Resume campaign](/api-reference/resume-google-ads-campaign) starts it again.\n\nPausing takes effect within a few minutes on Google's side. Spend already incurred today is still billed. Pausing an already-paused campaign succeeds and changes nothing.\n\nUnlike resuming, this needs no payment method on file, because it only reduces spend.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>A 409 means the change may or may not have been applied. The request to Google Ads timed out after it was sent. Read the campaign back with [Get campaign](/api-reference/get-google-ads-campaign) before retrying, or you can end up applying it twice.</Warning>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"pause_campaign_api_apps__app_id__google_ads_campaigns__campaign_id__pause_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","title":"Campaign Id"},"description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","example":"68b1c0d4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignStatusResponse"}}}},"400":{"description":"Google Ads rejected the request, for example ad text that breaks its policies or a budget below the currency's minimum. The response message carries Google's reason."},"409":{"description":"The request to Google Ads timed out after being sent, so the change may or may not have been applied. Read the campaign back before retrying."},"429":{"description":"Google Ads is rate limiting the account. Retry later."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or there is no campaign with this ID."}}}},"/api/apps/{app_id}/google-ads/campaigns/pause-all":{"post":{"summary":"Pause all Google Ads campaigns","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nStops every campaign on the account serving, in one call. Useful as a spend kill switch.\n\nThe response reports how many campaigns paused out of how many were tried. A `status` of `partial` means at least one did not pause and is still serving. The call does not roll the others back, so retry it or pause the remaining campaigns individually to see why.\n\nThere is no matching resume-all. Resume each campaign with [Resume campaign](/api-reference/resume-google-ads-campaign), which re-checks billing per campaign.\n\n<Warning>A 409 means the change may or may not have been applied. The request to Google Ads timed out after it was sent. Read the campaign back with [Get campaign](/api-reference/get-google-ads-campaign) before retrying, or you can end up applying it twice.</Warning>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"pause_all_campaigns_api_apps__app_id__google_ads_campaigns_pause_all_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PauseAllCampaignsResponse"}}}},"400":{"description":"Google Ads rejected the request, for example ad text that breaks its policies or a budget below the currency's minimum. The response message carries Google's reason."},"409":{"description":"The request to Google Ads timed out after being sent, so the change may or may not have been applied. Read the campaign back before retrying."},"429":{"description":"Google Ads is rate limiting the account. Retry later."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."}}}},"/api/apps/{app_id}/google-ads/campaigns/{campaign_id}/resume":{"post":{"summary":"Resume Google Ads campaign","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nStarts a paused campaign serving again.\n\nThis re-opens spend, so it carries the same requirements as creating a campaign: a payment method on file and an account in good standing, or the call returns a 402 and the campaign stays paused.\n\nA campaign Base44 paused itself does not stay resumed while the reason still holds. An unpaid balance or unverified conversion tracking pauses it again. Resolve the underlying block first. [Get campaign](/api-reference/get-google-ads-campaign) reports the campaign's state.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>A 409 means the change may or may not have been applied. The request to Google Ads timed out after it was sent. Read the campaign back with [Get campaign](/api-reference/get-google-ads-campaign) before retrying, or you can end up applying it twice.</Warning>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"resume_campaign_api_apps__app_id__google_ads_campaigns__campaign_id__resume_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to advertise.","title":"App Id"},"description":"ID of the app to advertise.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","title":"Campaign Id"},"description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","example":"68b1c0d4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignStatusResponse"}}}},"400":{"description":"Google Ads rejected the request, for example ad text that breaks its policies or a budget below the currency's minimum. The response message carries Google's reason."},"409":{"description":"The request to Google Ads timed out after being sent, so the change may or may not have been applied. Read the campaign back before retrying."},"429":{"description":"Google Ads is rate limiting the account. Retry later."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"No payment method on file, or the workspace has an unpaid balance."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or there is no campaign with this ID."}}}},"/api/apps/{app_id}/google-ads/campaigns/estimate":{"post":{"summary":"Estimate a Google Ads campaign budget","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nAsks Google to forecast the clicks and spend a set of keyword themes would produce, so you can show a projection before a campaign is created.\n\nSend the themes you are considering and the daily budget you have in mind, both as they would go to [Create campaign](/api-reference/create-google-ads-campaign). Nothing is saved.\n\nThe forecast comes from Google's own planner, so it needs a connected Google Ads account that has finished provisioning. An account that exists but has no Google customer ID yet is rejected with a 400; wait for provisioning to finish and retry.\n\n`estimated_monthly_cost` is in micros and covers 30 days.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"estimate_campaign_budget_api_apps__app_id__google_ads_campaigns_estimate_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app you are planning a Google Ads campaign for.","title":"App Id"},"description":"ID of the app you are planning a Google Ads campaign for.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BudgetEstimateRequest"}}}},"responses":{"200":{"description":"Google's forecast for the themes and budget you sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsBudgetEstimate"}}}},"400":{"description":"The Google Ads account has not finished provisioning, so it has no Google customer ID yet."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/ai/hero-preview":{"get":{"summary":"Get a Google Ads hero preview","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nWrites one sample search result for the app, to show someone what their ad could look like before they connect a Google Ads account.\n\nThis is illustrative copy, not a campaign, and nothing is saved. It is built from the app's own description, so it works before there is an account, a campaign, or any business details on file.\n\nCheck `source` before you present this as generated copy: `fallback` means the model did not run or came back empty and you are looking at generic text. The response is a 200 either way.\n\n<Note>This endpoint runs a language model and draws on a shared quota of 20 requests per minute per app, which every Google Ads AI endpoint debits. Enterprise workspaces get a higher quota. A 429 means the quota is spent, not that this endpoint has its own limit.</Note>\n\n<Note>The copy is written in the language you send in the `Accept-Language` header. Omit the header and you get English — the app's own language is not consulted — so send it explicitly for a non-English app.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"hero_preview_api_apps__app_id__google_ads_ai_hero_preview_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app you are planning a Google Ads campaign for.","title":"App Id"},"description":"ID of the app you are planning a Google Ads campaign for.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"Accept-Language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Language to return generated text in, as a standard `Accept-Language` value. An unsupported language falls back to English.","title":"Accept-Language"},"description":"Language to return generated text in, as a standard `Accept-Language` value. An unsupported language falls back to English.","example":"de"}],"responses":{"200":{"description":"A sample search result for the app.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsHeroPreview"}}}},"429":{"description":"The shared AI quota for this app is spent. Retry in a minute."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."}}}},"/api/apps/{app_id}/google-ads/ai/generate-copy":{"post":{"summary":"Generate Google Ads ad copy","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nWrites headlines and descriptions for a campaign from a business name and description.\n\nNothing is saved. Take the fields you want and pass them to [Create campaign](/api-reference/create-google-ads-campaign), or to [Create campaign brief](/api-reference/create-google-ads-campaign-brief) to keep them for later.\n\nCheck `source` before you present this as generated copy. Anything other than `ai` means the model did not produce copy and the response is fallback text derived from what you sent, returned under a 200. `headlines` and `descriptions` can also come back empty.\n\n<Note>This endpoint runs a language model and draws on a shared quota of 20 requests per minute per app, which every Google Ads AI endpoint debits. Enterprise workspaces get a higher quota. A 429 means the quota is spent, not that this endpoint has its own limit.</Note>\n\n<Note>The copy is written in the language you send in the `Accept-Language` header. Omit the header and you get English — the app's own language is not consulted — so send it explicitly for a non-English app.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"generate_ad_copy_api_apps__app_id__google_ads_ai_generate_copy_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app you are planning a Google Ads campaign for.","title":"App Id"},"description":"ID of the app you are planning a Google Ads campaign for.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"Accept-Language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Language to return generated text in, as a standard `Accept-Language` value. An unsupported language falls back to English.","title":"Accept-Language"},"description":"Language to return generated text in, as a standard `Accept-Language` value. An unsupported language falls back to English.","example":"de"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateCopyRequest"}}}},"responses":{"200":{"description":"Suggested headlines and descriptions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsAdCopy"}}}},"429":{"description":"The shared AI quota for this app is spent. Retry in a minute."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/suggestions/text-assets":{"post":{"summary":"Suggest Google Ads text assets","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSuggests text assets for a Performance Max campaign, one group per asset type you ask for.\n\nSend `asset_types` with any of `HEADLINE`, `LONG_HEADLINE`, `DESCRIPTION`, `BUSINESS_NAME` and `CALL_TO_ACTION_SELECTION`. Only the types you ask for come back, so read the response by key rather than assuming all five are present. `BUSINESS_NAME` is a single string; the rest are arrays. `CALL_TO_ACTION_SELECTION` is a fixed set of values Google accepts rather than generated copy.\n\nNothing is saved. An asset type can come back as an empty array when the model returns nothing usable for it, under a 200.\n\n<Note>This endpoint runs a language model and draws on a shared quota of 20 requests per minute per app, which every Google Ads AI endpoint debits. Enterprise workspaces get a higher quota. A 429 means the quota is spent, not that this endpoint has its own limit.</Note>\n\n<Note>The copy is written in the language you send in the `Accept-Language` header. Omit the header and you get English — the app's own language is not consulted — so send it explicitly for a non-English app.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"suggest_text_assets_api_apps__app_id__google_ads_suggestions_text_assets_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app you are planning a Google Ads campaign for.","title":"App Id"},"description":"ID of the app you are planning a Google Ads campaign for.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"Accept-Language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Language to return generated text in, as a standard `Accept-Language` value. An unsupported language falls back to English.","title":"Accept-Language"},"description":"Language to return generated text in, as a standard `Accept-Language` value. An unsupported language falls back to English.","example":"de"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TextAssetSuggestionsRequest"}}}},"responses":{"200":{"description":"One group per asset type you asked for.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsTextAssets"}}}},"429":{"description":"The shared AI quota for this app is spent. Retry in a minute."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/suggestions/search-themes":{"post":{"summary":"Suggest Google Ads search themes","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSuggests search themes for a Performance Max campaign.\n\nSearch themes tell Google what the campaign is about when there are no keywords to match on. Nothing is saved: pass the ones you want to [Create campaign](/api-reference/create-google-ads-campaign).\n\nYou get at most 10 themes, each trimmed to 80 characters, and themes naming the platform itself are dropped. An empty list is a 200, so check the array rather than the status code.\n\n<Note>This endpoint runs a language model and draws on a shared quota of 20 requests per minute per app, which every Google Ads AI endpoint debits. Enterprise workspaces get a higher quota. A 429 means the quota is spent, not that this endpoint has its own limit.</Note>\n\n<Note>The copy is written in the language you send in the `Accept-Language` header. Omit the header and you get English — the app's own language is not consulted — so send it explicitly for a non-English app.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"suggest_search_themes_api_apps__app_id__google_ads_suggestions_search_themes_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app you are planning a Google Ads campaign for.","title":"App Id"},"description":"ID of the app you are planning a Google Ads campaign for.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"Accept-Language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Language to return generated text in, as a standard `Accept-Language` value. An unsupported language falls back to English.","title":"Accept-Language"},"description":"Language to return generated text in, as a standard `Accept-Language` value. An unsupported language falls back to English.","example":"de"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchThemesRequest"}}}},"responses":{"200":{"description":"Suggested search themes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsSearchThemes"}}}},"429":{"description":"The shared AI quota for this app is spent. Retry in a minute."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/suggestions/enhance-prompt":{"post":{"summary":"Enhance a Google Ads image prompt","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRewrites a short image prompt into a fuller one that produces better Performance Max creative.\n\nSend the text someone typed as `raw_prompt`. Nothing is saved.\n\nAn empty or whitespace-only `raw_prompt` returns an empty `enhanced_prompt` immediately, without running a model and without spending quota. If the model fails, you get your original prompt back as `enhanced_prompt`, so compare it against `original_prompt` when you need to know whether the rewrite actually happened.\n\n<Note>This endpoint runs a language model and draws on a shared quota of 20 requests per minute per app, which every Google Ads AI endpoint debits. Enterprise workspaces get a higher quota. A 429 means the quota is spent, not that this endpoint has its own limit.</Note>\n\n<Note>The copy is written in the language you send in the `Accept-Language` header. Omit the header and you get English — the app's own language is not consulted — so send it explicitly for a non-English app.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"enhance_prompt_api_apps__app_id__google_ads_suggestions_enhance_prompt_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app you are planning a Google Ads campaign for.","title":"App Id"},"description":"ID of the app you are planning a Google Ads campaign for.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"Accept-Language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Language to return generated text in, as a standard `Accept-Language` value. An unsupported language falls back to English.","title":"Accept-Language"},"description":"Language to return generated text in, as a standard `Accept-Language` value. An unsupported language falls back to English.","example":"de"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnhancePromptRequest"}}}},"responses":{"200":{"description":"The rewritten prompt.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsEnhancedPrompt"}}}},"429":{"description":"The shared AI quota for this app is spent. Retry in a minute."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/troubleshoot/ask":{"post":{"summary":"Ask the Google Ads assistant","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nAnswers a plain-language question about the app's Google Ads campaigns.\n\nThe answer is prose written for a person, three to five sentences with no markdown, so read it or show it to someone rather than parsing it. The assistant is a language model with no access to the account's numbers, so it gives general advice shaped by your question rather than findings from your data. Read the reporting endpoints such as [Get Google Ads performance dashboard](/api-reference/get-google-ads-performance-dashboard) for the actual figures.\n\nPass earlier turns in `history` to ask a follow-up. Only the last ten turns reach the model, and anything before that is dropped with nothing in the response saying so, so keep a long conversation summarized on your side.\n\n<Warning>An empty `reply` means the model returned nothing, and it comes back under a 200 exactly like a real answer. There is no field that distinguishes the two, so check for an empty string and retry rather than treating it as the assistant having nothing to say.</Warning>\n\nThis endpoint is limited to 20 requests a minute per app, shared with the other Google Ads endpoints that run a language model.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"troubleshoot_ask_api_apps__app_id__google_ads_troubleshoot_ask_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"AskGoogleAdsAssistant","type":"object","required":["question"],"properties":{"question":{"type":"string","description":"What you want to ask about the app's Google Ads campaigns, in plain language.","example":"Why is my Performance Max campaign spending its budget without converting?"},"campaign_id":{"type":"string","description":"A campaign ID to mention in the question. It is passed to the model as text for context only, so the assistant does not read the campaign's data from it.","example":"68b1c0d4e7b91d003c45a1f2"},"history":{"type":"array","description":"Earlier turns of the conversation, oldest first. Only the last ten are used.","items":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","description":"Who said it. Use `user` for your own turns and `assistant` for the replies. Any value other than `user` is treated as the assistant.","example":"user"},"content":{"type":"string","description":"What was said in that turn.","example":"Which campaign is spending the most?"}}},"example":[{"role":"user","content":"Which campaign is spending the most?"}]}}},"example":{"question":"Why is my Performance Max campaign spending its budget without converting?","campaign_id":"68b1c0d4e7b91d003c45a1f2"}}}},"responses":{"200":{"description":"The assistant's answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TroubleshootReply"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"429":{"description":"The app has used up its 20 language-model requests a minute. Retry later."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/campaign-briefs":{"post":{"summary":"Create Google Ads campaign brief","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSaves a new campaign brief.\n\nA campaign brief is saved input for a campaign you have not launched yet: the business details, the keywords, the budget you have in mind, and the ad copy. Base44 stores it so you can build a campaign over several requests instead of one, and nothing about a brief reaches Google Ads until you create a campaign from it.\n\nEvery field is optional, so you can save a partial brief and fill it in later with [Update campaign brief](/api-reference/update-google-ads-campaign-brief). Set `platform_type` to `SMART` or `PERFORMANCE_MAX`. It is stored as sent and is not checked against that list.\n\n<Warning>This creates a new brief every time it is called. There is nothing that keeps one brief per campaign type, so a retried request leaves you with duplicates. Read the list first and use [Update campaign brief](/api-reference/update-google-ads-campaign-brief) when you already have one.</Warning>\n\n<Note>The `language` you send is stored as-is and echoed back here. [List campaign briefs](/api-reference/list-google-ads-campaign-briefs) and [Get campaign brief](/api-reference/get-google-ads-campaign-brief) report it normalized instead. A regional code is reduced to its primary subtag, so `pt-BR` reads back as `pt`, and a code Google Ads does not support reads back as `en`. Send a plain two-letter code to get the same value from every endpoint.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"create_campaign_brief_api_apps__app_id__google_ads_campaign_briefs_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"CreateCampaignBrief","type":"object","properties":{"platform_type":{"type":"string","description":"Which campaign type the brief is for, `SMART` or `PERFORMANCE_MAX`. Stored as sent and not checked against that list.","default":"SMART"},"business_name":{"type":"string","description":"Business name to advertise."},"business_description":{"type":"string","description":"What the business does. Used when Base44 generates copy."},"landing_page_url":{"type":"string","description":"URL the ads will send clicks to."},"language":{"type":"string","description":"Language the ad copy is in, as a lowercase two-letter code such as `en` or `de`.","default":"en"},"keywords":{"type":"array","items":{"type":"string"},"description":"Keywords the campaign should match."},"target_audience":{"type":"string","description":"Free-text description of who the campaign is for."},"daily_budget_micros":{"type":"integer","description":"Planned daily budget in micros of the account currency, so `15000000` is 15.00."},"geo_targets":{"type":"array","items":{"type":"string"},"description":"Google Ads geo target constant IDs to target."},"headlines":{"type":"array","items":{"type":"string"},"description":"Ad headlines."},"descriptions":{"type":"array","items":{"type":"string"},"description":"Ad description lines."},"schedule_type":{"type":"string","description":"When the campaign runs. Use `always` to run continuously, or `custom` to use `schedule_days`.","default":"always"},"schedule_days":{"type":"array","description":"Day and hour windows to run in, used only when `schedule_type` is `custom`.","items":{"type":"object","properties":{"day":{"type":"string","description":"Day of the week, such as `MONDAY`."},"start_hour":{"type":"integer","description":"Hour the window opens, 0 to 23."},"end_hour":{"type":"integer","description":"Hour the window closes, 0 to 23."}}}}}},"example":{"platform_type":"SMART","business_name":"Nordwind Furniture","business_description":"Handmade oak furniture, delivered across Germany.","landing_page_url":"https://example.com/spring","language":"de","keywords":["oak furniture","handmade table"],"daily_budget_micros":15000000,"geo_targets":["1003854"]}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignBriefResource"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Google Ads campaign briefs","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the app's saved campaign briefs, most recently changed first.\n\nA campaign brief is saved input for a campaign you have not launched yet: the business details, the keywords, the budget you have in mind, and the ad copy. Base44 stores it so you can build a campaign over several requests instead of one, and nothing about a brief reaches Google Ads until you create a campaign from it.\n\nPass `platform_type` to return only the briefs for one campaign type. Any other value returns an empty list rather than an error, so check the value you send.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"list_campaign_briefs_api_apps__app_id__google_ads_campaign_briefs_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"platform_type","in":"query","required":false,"schema":{"type":"string","description":"Filter to SMART or PERFORMANCE_MAX","default":"","title":"Platform Type"},"description":"Filter to SMART or PERFORMANCE_MAX"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CampaignBriefResource"},"title":"Response 200 List Campaign Briefs Api Apps  App Id  Google Ads Campaign Briefs Get"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."}}}},"/api/apps/{app_id}/google-ads/campaign-briefs/{brief_id}":{"get":{"summary":"Get Google Ads campaign brief","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns one saved campaign brief.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"get_campaign_brief_api_apps__app_id__google_ads_campaign_briefs__brief_id__get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"brief_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the brief, as returned in `id` by [List campaign briefs](/api-reference/list-google-ads-campaign-briefs).","title":"Brief Id"},"description":"ID of the brief, as returned in `id` by [List campaign briefs](/api-reference/list-google-ads-campaign-briefs).","example":"68b1c0d4e7b91d003c45a1f5"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignBriefResource"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"There is no brief with this ID on this app."}}},"patch":{"summary":"Update Google Ads campaign brief","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nChanges a saved campaign brief.\n\nSend only the fields you want to change. A field you leave out keeps its stored value, so this is a partial update rather than a replacement.\n\n<Note>The `language` you send is stored as-is and echoed back here. [List campaign briefs](/api-reference/list-google-ads-campaign-briefs) and [Get campaign brief](/api-reference/get-google-ads-campaign-brief) report it normalized instead. A regional code is reduced to its primary subtag, so `pt-BR` reads back as `pt`, and a code Google Ads does not support reads back as `en`. Send a plain two-letter code to get the same value from every endpoint.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"update_campaign_brief_api_apps__app_id__google_ads_campaign_briefs__brief_id__patch","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"brief_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the brief, as returned in `id` by [List campaign briefs](/api-reference/list-google-ads-campaign-briefs).","title":"Brief Id"},"description":"ID of the brief, as returned in `id` by [List campaign briefs](/api-reference/list-google-ads-campaign-briefs).","example":"68b1c0d4e7b91d003c45a1f5"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"UpdateCampaignBrief","type":"object","properties":{"platform_type":{"type":"string","description":"Which campaign type the brief is for, `SMART` or `PERFORMANCE_MAX`. Stored as sent and not checked against that list.","default":"SMART"},"business_name":{"type":"string","description":"Business name to advertise."},"business_description":{"type":"string","description":"What the business does. Used when Base44 generates copy."},"landing_page_url":{"type":"string","description":"URL the ads will send clicks to."},"language":{"type":"string","description":"Language the ad copy is in, as a lowercase two-letter code such as `en` or `de`.","default":"en"},"keywords":{"type":"array","items":{"type":"string"},"description":"Keywords the campaign should match."},"target_audience":{"type":"string","description":"Free-text description of who the campaign is for."},"daily_budget_micros":{"type":"integer","description":"Planned daily budget in micros of the account currency, so `15000000` is 15.00."},"geo_targets":{"type":"array","items":{"type":"string"},"description":"Google Ads geo target constant IDs to target."},"headlines":{"type":"array","items":{"type":"string"},"description":"Ad headlines."},"descriptions":{"type":"array","items":{"type":"string"},"description":"Ad description lines."},"schedule_type":{"type":"string","description":"When the campaign runs. Use `always` to run continuously, or `custom` to use `schedule_days`.","default":"always"},"schedule_days":{"type":"array","description":"Day and hour windows to run in, used only when `schedule_type` is `custom`.","items":{"type":"object","properties":{"day":{"type":"string","description":"Day of the week, such as `MONDAY`."},"start_hour":{"type":"integer","description":"Hour the window opens, 0 to 23."},"end_hour":{"type":"integer","description":"Hour the window closes, 0 to 23."}}}}}},"example":{"daily_budget_micros":20000000,"keywords":["oak furniture","dining table"]}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignBriefResource"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"There is no brief with this ID on this app."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete Google Ads campaign brief","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nDeletes a saved campaign brief.\n\nThis removes saved input only. A campaign you already created from the brief keeps running, and deleting the brief does not change it.\n\nAnyone with access to the app can delete any of its briefs, not only the person who created it.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"delete_campaign_brief_api_apps__app_id__google_ads_campaign_briefs__brief_id__delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"brief_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the brief, as returned in `id` by [List campaign briefs](/api-reference/list-google-ads-campaign-briefs).","title":"Brief Id"},"description":"ID of the brief, as returned in `id` by [List campaign briefs](/api-reference/list-google-ads-campaign-briefs).","example":"68b1c0d4e7b91d003c45a1f5"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignStatusResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"There is no brief with this ID on this app."}}}},"/api/apps/{app_id}/google-ads/campaign-briefs/generate":{"post":{"summary":"Generate Google Ads campaign brief","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nWrites a campaign brief for you from a few business details, and saves it.\n\nSend the business name, what it does, its landing page, and any keywords you already have. Base44 writes ad headlines and descriptions from those details, and saves them as a new brief. `source` on the response is `ai`, which is how you tell a generated brief from one you wrote.\n\nThe copy is written in whichever language the details you send are in, and that language is recorded on the brief. Base44 does not open the landing page to work this out, so it passes the URL along as text and nothing more. A German site described in English produces English copy.\n\nThis runs a language model, so it is slower than the other endpoints here and it draws on a per-app quota shared with the other Google Ads generation endpoints. The default is 20 requests a minute, and your workspace's plan can raise it, so treat the 429 rather than a fixed count as the signal you have run out.\n\n<Warning>A generation failure still returns a 200 with a saved brief. Base44 records the failure on its side and hands back a brief whose `headlines` and `descriptions` are empty, still marked `source: ai`. Check that those two lists are non-empty before you use the result.</Warning>\n\n<Warning>This creates a new brief every time it is called. There is nothing that keeps one brief per campaign type, so a retried request leaves you with duplicates. Read the list first and use [Update campaign brief](/api-reference/update-google-ads-campaign-brief) when you already have one.</Warning>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"generate_campaign_brief_api_apps__app_id__google_ads_campaign_briefs_generate_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"GenerateCampaignBrief","type":"object","properties":{"platform_type":{"type":"string","description":"Which campaign type the brief is for, `SMART` or `PERFORMANCE_MAX`. Stored as sent and not checked against that list.","default":"SMART"},"business_name":{"type":"string","description":"Business name to advertise."},"business_description":{"type":"string","description":"What the business does. Used when Base44 generates copy."},"landing_page_url":{"type":"string","description":"URL the ads will send clicks to."},"keywords":{"type":"array","items":{"type":"string"},"description":"Keywords the campaign should match."}}},"example":{"platform_type":"SMART","business_name":"Nordwind Furniture","business_description":"Handmade oak furniture, delivered across Germany.","landing_page_url":"https://example.com/spring","keywords":["oak furniture"]}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignBriefResource"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"429":{"description":"The app has used up its Google Ads generation quota for the current window. Retry later."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/campaigns/ai-suggestions":{"post":{"summary":"Suggest Google Ads campaigns","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nTurns a plain description of a business into two or three complete campaigns you can preview side by side and launch.\n\nEach suggestion carries a `payload` you can send straight to [Create campaign](/api-reference/create-google-ads-campaign). The approaches differ by goal: Smart Search for traffic, Smart Search for leads, and Performance Max for leads. Nothing is saved.\n\nYou get at most three suggestions. `suggestion_count` accepts up to 5, but only three approaches exist, so 4 and 5 return the same three as 3 does.\n\nThe Performance Max suggestion cannot launch as-is: `payload.logo_url` and `payload.images` come back empty because this endpoint does not generate images, and Google requires both. Collect them before you send that payload.\n\nCheck `source` before presenting the copy as generated: `fallback` means the model did not run or returned nothing. `title` and `subtitle` are always English, even when the copy inside `payload` is in the app's language, so translate them yourself if you show them to someone.\n\nThis one call runs two language-model requests and debits the shared quota twice.\n\n<Note>This endpoint runs a language model and draws on a shared quota of 20 requests per minute per app, which every Google Ads AI endpoint debits. Enterprise workspaces get a higher quota. A 429 means the quota is spent, not that this endpoint has its own limit.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"ai_campaign_suggestions_api_apps__app_id__google_ads_campaigns_ai_suggestions_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app you are planning a Google Ads campaign for.","title":"App Id"},"description":"ID of the app you are planning a Google Ads campaign for.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AICampaignBrief"}}}},"responses":{"200":{"description":"Campaigns you can preview and launch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsCampaignSuggestions"}}}},"429":{"description":"The shared AI quota for this app is spent. This endpoint debits it twice. Retry in a minute."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/campaigns/{campaign_id}/optimization-suggestions":{"get":{"summary":"List Google Ads campaign optimization suggestions","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns what to change on one campaign: budget, tracking and policy problems Base44 spotted, plus Google Ads' own recommendations for that campaign.\n\nBase44's own suggestions are computed from its stored metrics for the last 14 days, so they cost no Google call. Google's recommendations for this campaign are merged in on top and carry `source` set to `google` along with `recommendation_type`; Base44's own suggestions have neither field, which is how you tell them apart.\n\nWhen Google cannot be reached you still get a 200 with Base44's own suggestions only, and nothing in the response says the Google half is missing. An empty array means nothing needs attention.\n\nA suggestion with `recommendation_type` `CAMPAIGN_BUDGET` can be applied with [Apply budget recommendation](/api-reference/apply-a-google-ads-budget-recommendation).\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"campaign_optimization_suggestions_api_apps__app_id__google_ads_campaigns__campaign_id__optimization_suggestions_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","title":"Campaign Id"},"description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","example":"68b1c0d4e7b91d003c45a1f2"}],"responses":{"200":{"description":"What to change on this campaign.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AccountInsight"},"title":"GoogleAdsCampaignOptimizationSuggestions"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or there is no campaign with this ID."}}}},"/api/apps/{app_id}/google-ads/campaigns/{campaign_id}/recommendations/apply-budget":{"post":{"summary":"Apply a Google Ads budget recommendation","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nApplies a Google Ads budget recommendation to a campaign, raising what it can spend per day.\n\nTake `recommendation_resource_name`, `recommended_daily_budget_micros` and `recommendation_type` from a suggestion returned by [List campaign optimization suggestions](/api-reference/list-google-ads-campaign-optimization-suggestions) whose `recommendation_type` is `CAMPAIGN_BUDGET`.\n\nYou do not choose the amount. Base44 re-reads the recommendation from Google and applies Google's current figure. `recommended_daily_budget_micros` is the amount you are confirming, not a value to set: if Google's figure has changed since you read the suggestion, the call is rejected with a 409 so nobody raises a budget to a number they never saw. Read the suggestions again and resend.\n\nThis changes real spend. Applying the same recommendation twice is safe: the second call changes nothing and returns `already_applied` set to `true`.\n\nSome recommendations cover a budget shared by several campaigns. Applying one raises the budget for all of them, so Base44 rejects it with a 409 unless every campaign it touches is one you manage here.\n\n<Warning>A 409 means the change may or may not have been applied. The request to Google Ads timed out after it was sent. Read the campaign back with [Get campaign](/api-reference/get-google-ads-campaign) before retrying, or you can end up applying it twice.</Warning>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"apply_budget_recommendation_api_apps__app_id__google_ads_campaigns__campaign_id__recommendations_apply_budget_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","title":"Campaign Id"},"description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","example":"68b1c0d4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/_ApplyBudgetRecommendationRequest"}}}},"responses":{"200":{"description":"The campaign's budget after the change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsAppliedBudgetRecommendation"}}}},"402":{"description":"The account has no usable payment method, so its budget cannot be raised."},"409":{"description":"The recommendation does not apply to this campaign, its amount changed since you read it, it covers campaigns not managed here, or the change to Google Ads timed out and may already have been applied."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or there is no campaign with this ID."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/accounts/{account_id}/business-profile/link":{"post":{"summary":"Link a Google Business Profile","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nLinks a Google Business Profile location to the account, so Smart campaigns created from now on can show its address and hours.\n\nSend the location as `locations/<id>`, which is how Google names it. A malformed value comes back as a 400.\n\n<Note>This affects campaigns created after the link. Campaigns that already exist are not changed.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"link_business_profile_api_apps__app_id__google_ads_accounts__account_id__business_profile_link_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"account_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account that belongs to a different app returns a 404.","title":"Account Id"},"description":"ID of the Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account that belongs to a different app returns a 404.","example":"68b1c0d4e7b91d003c45a1f8"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"LinkBusinessProfile","type":"object","required":["location_id"],"properties":{"location_id":{"type":"string","description":"Google Business Profile location, as Google names it: `locations/<id>`."}}},"example":{"location_id":"locations/12345"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BusinessProfileLinkResponse"}}}},"400":{"description":"The `location_id` is not a Google Business Profile location name."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or no account with this ID belongs to this app."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/accounts/{account_id}/business-profile/unlink":{"post":{"summary":"Unlink the Google Business Profile","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRemoves the Google Business Profile location from the account.\n\n<Warning>This only stops Base44 attaching the profile to Smart campaigns it creates from now on. It does not remove the profile from campaigns that already exist, so ads currently serving keep showing the business address and hours. Edit or recreate those campaigns to change what they show.</Warning>\n\nThis does not change the Business Profile itself. Unlinking when nothing is linked succeeds and returns an empty `business_profile_location_id`.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"unlink_business_profile_api_apps__app_id__google_ads_accounts__account_id__business_profile_unlink_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"account_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account that belongs to a different app returns a 404.","title":"Account Id"},"description":"ID of the Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account that belongs to a different app returns a 404.","example":"68b1c0d4e7b91d003c45a1f8"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BusinessProfileLinkResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or no account with this ID belongs to this app."}}}},"/api/apps/{app_id}/google-ads/analytics/dashboard":{"get":{"summary":"Get Google Ads performance dashboard","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the account's spend, clicks, impressions, conversions and return on ad spend for a date window, each with its change against the previous window of the same length, plus a per-campaign breakdown.\n\nBase44 reads these numbers from Google Ads on every call. When Google is unavailable it falls back to its own nightly copy, which is the same shape minus `trend` and `conversion_goals`: write your client to treat both as optional rather than assuming the live shape.\n\nArchived campaigns stay in both the totals and the breakdown, so the rows always sum to the figures above them.\n\n`conversions` and `conversions_trend` are `null` for an account that records no conversions and has no conversion tracking configured, so a real zero stays distinguishable from an untracked account. [List conversion actions](/api-reference/list-google-ads-conversion-actions) shows whether the account has any goals at all.\n\nSet `include_campaign_trend` to `false` to drop the per-campaign daily sparkline. It costs one Google Ads row per campaign per day, so leaving it on is noticeably slower on an account with many campaigns and a long window.\n\n<Note>`start_date` and `end_date` are inclusive and must both be `YYYY-MM-DD`. Anything else is rejected with a 400.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"get_dashboard_api_apps__app_id__google_ads_analytics_dashboard_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads reporting you want to read.","title":"App Id"},"description":"ID of the app whose Google Ads reporting you want to read.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"start_date","in":"query","required":true,"schema":{"type":"string","description":"First day of the window, as `YYYY-MM-DD`. Inclusive.","title":"Start Date"},"description":"First day of the window, as `YYYY-MM-DD`. Inclusive.","example":"2026-08-01"},{"name":"end_date","in":"query","required":true,"schema":{"type":"string","description":"Last day of the window, as `YYYY-MM-DD`. Inclusive.","title":"End Date"},"description":"Last day of the window, as `YYYY-MM-DD`. Inclusive.","example":"2026-08-31"},{"name":"include_campaign_trend","in":"query","required":false,"schema":{"type":"boolean","description":"Include each campaign's daily series in `campaign_breakdown[].trend`. Set it to `false` to leave the series out, which is significantly faster on an account with many campaigns or a long window.","default":true,"title":"Include Campaign Trend"},"description":"Include each campaign's daily series in `campaign_breakdown[].trend`. Set it to `false` to leave the series out, which is significantly faster on an account with many campaigns or a long window.","example":true}],"responses":{"200":{"description":"The account's totals for the window, plus a row per campaign.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DashboardResponse"}}}},"400":{"description":"`start_date` or `end_date` is not `YYYY-MM-DD`."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/analytics/trend":{"get":{"summary":"Get Google Ads metric trend","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns one metric as a time series for a date window, bucketed daily, weekly or monthly.\n\n`metric` is one of `impressions`, `clicks`, `cost_micros`, `conversions`, `conversions_value`, `ctr`, or `average_cpc_micros`. Anything else is rejected with a 400.\n\n`ctr` and `average_cpc_micros` are rates, so each bucket is computed from the underlying counts rather than by adding up the daily rates. A bucket with no impressions has a `ctr` of `0`, and one with no clicks has an `average_cpc_micros` of `0`.\n\n`granularity` is `DAILY`, `WEEKLY` or `MONTHLY` and defaults to `DAILY`. Weekly buckets are anchored to Monday. Buckets with no activity are not returned, so expect gaps rather than zeroes.\n\nAn account with no synced campaigns returns an empty list.\n\n<Note>These numbers come from Base44's own nightly copy of your Google Ads metrics, not from Google directly, so the last few hours of activity can be missing.</Note>\n\n<Note>`start_date` and `end_date` are inclusive and must both be `YYYY-MM-DD`. Anything else is rejected with a 400.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"get_trend_api_apps__app_id__google_ads_analytics_trend_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads reporting you want to read.","title":"App Id"},"description":"ID of the app whose Google Ads reporting you want to read.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"metric","in":"query","required":true,"schema":{"type":"string","description":"The metric to chart: `impressions`, `clicks`, `cost_micros`, `conversions`, `conversions_value`, `ctr`, or `average_cpc_micros`.","title":"Metric"},"description":"The metric to chart: `impressions`, `clicks`, `cost_micros`, `conversions`, `conversions_value`, `ctr`, or `average_cpc_micros`.","example":"clicks"},{"name":"start_date","in":"query","required":true,"schema":{"type":"string","description":"First day of the window, as `YYYY-MM-DD`. Inclusive.","title":"Start Date"},"description":"First day of the window, as `YYYY-MM-DD`. Inclusive.","example":"2026-08-01"},{"name":"end_date","in":"query","required":true,"schema":{"type":"string","description":"Last day of the window, as `YYYY-MM-DD`. Inclusive.","title":"End Date"},"description":"Last day of the window, as `YYYY-MM-DD`. Inclusive.","example":"2026-08-31"},{"name":"granularity","in":"query","required":false,"schema":{"type":"string","description":"Bucket size: `DAILY`, `WEEKLY` (Monday-anchored), or `MONTHLY`.","default":"DAILY","title":"Granularity"},"description":"Bucket size: `DAILY`, `WEEKLY` (Monday-anchored), or `MONTHLY`.","example":"DAILY"}],"responses":{"200":{"description":"One entry per bucket with activity, oldest first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/MetricTrendPoint"},"title":"MetricTrend"}}}},"400":{"description":"`metric` is not one of the supported metrics, `granularity` is not `DAILY`, `WEEKLY` or `MONTHLY`, or a date is not `YYYY-MM-DD`."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/analytics/insights":{"get":{"summary":"List Google Ads insights","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the account's suggestion feed for a date window: what to change, on which campaigns, and how urgent it is.\n\nThe feed merges Base44's own suggestions with Google Ads' budget recommendations and ad-strength findings. Base44 removes the overlaps, so a campaign that both sides flag for the same reason appears once. Suggestions you dismissed are left out.\n\n`action_payload.campaign_ids` holds Base44 campaign IDs on Base44's own suggestions, and Google Ads campaign IDs on ad-strength ones (`recommendation_type` `AD_STRENGTH_DROP`). Check `recommendation_type` before passing an ID to another endpoint.\n\nGoogle's half of the feed is best-effort: when Google Ads is unavailable you get Base44's suggestions alone, under a 200 and with nothing in the response to say so.\n\nAn account with no synced campaigns returns an empty list.\n\n<Note>`start_date` and `end_date` are inclusive and must both be `YYYY-MM-DD`. Anything else is rejected with a 400.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"get_account_insights_api_apps__app_id__google_ads_analytics_insights_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads reporting you want to read.","title":"App Id"},"description":"ID of the app whose Google Ads reporting you want to read.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"start_date","in":"query","required":true,"schema":{"type":"string","description":"First day of the window, as `YYYY-MM-DD`. Inclusive.","title":"Start Date"},"description":"First day of the window, as `YYYY-MM-DD`. Inclusive.","example":"2026-08-01"},{"name":"end_date","in":"query","required":true,"schema":{"type":"string","description":"Last day of the window, as `YYYY-MM-DD`. Inclusive.","title":"End Date"},"description":"Last day of the window, as `YYYY-MM-DD`. Inclusive.","example":"2026-08-31"}],"responses":{"200":{"description":"The account's suggestions, most urgent first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AccountInsight"},"title":"Insights"}}}},"400":{"description":"`start_date` or `end_date` is not `YYYY-MM-DD`."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/analytics/insights/dismiss":{"post":{"summary":"Dismiss Google Ads insight","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nHides one suggestion from the account's feed for good.\n\nPass the `id` of a suggestion from [List insights](/api-reference/list-google-ads-insights) as `insight_id`. The ID is not checked against the live feed, so an ID that no longer exists is accepted and returns a 200.\n\nDismissal is per account, not per user: everyone in the workspace stops seeing the suggestion. It cannot be undone through the API, and it only affects this feed. Base44's daily recommendation emails keep covering the same finding.\n\nBase44 keeps a bounded number of dismissals per account, so dismissing many suggestions eventually lets the oldest ones reappear.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"dismiss_account_insight_api_apps__app_id__google_ads_analytics_insights_dismiss_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads reporting you want to read.","title":"App Id"},"description":"ID of the app whose Google Ads reporting you want to read.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"insight_id","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":200,"description":"`id` of the suggestion to hide, from [List insights](/api-reference/list-google-ads-insights).","title":"Insight Id"},"description":"`id` of the suggestion to hide, from [List insights](/api-reference/list-google-ads-insights).","example":"increase_budget:21458812345"}],"responses":{"200":{"description":"The suggestion is hidden.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InsightDismissResponse"}}}},"422":{"description":"`insight_id` is missing, empty, or longer than 200 characters."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."}}}},"/api/apps/{app_id}/google-ads/analytics/search-terms":{"get":{"summary":"List Google Ads search terms","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the search terms that triggered the account's ads, ordered by impressions.\n\nPass either `date_range` or both `start_date` and `end_date`. Sending only one of the two dates is rejected with a 400, as is a `start_date` after `end_date`. Explicit dates win over `date_range`.\n\n`date_range` accepts `TODAY`, `YESTERDAY`, `LAST_7_DAYS`, `LAST_14_DAYS`, `LAST_30_DAYS`, `LAST_90_DAYS`, `THIS_WEEK_MON_TODAY`, `THIS_WEEK_SUN_TODAY`, `LAST_WEEK_MON_SUN`, `LAST_WEEK_SUN_SAT`, `LAST_BUSINESS_WEEK`, `THIS_MONTH`, and `LAST_MONTH`, and defaults to `LAST_30_DAYS`. `LAST_7_DAYS`, `LAST_30_DAYS` and `LAST_90_DAYS` count back from today in the account's own time zone and include today; the other values are Google Ads' windows, which end yesterday.\n\nAt most 100 terms come back and there is no paging, so a busy account returns only its top 100. Terms are gathered from every campaign type on the account and merged into one list. When one of those reads fails the rest are still returned, under a 200 and with nothing in the response to say some terms are missing.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"get_search_terms_api_apps__app_id__google_ads_analytics_search_terms_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads reporting you want to read.","title":"App Id"},"description":"ID of the app whose Google Ads reporting you want to read.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"date_range","in":"query","required":false,"schema":{"type":"string","description":"A preset window, for example `LAST_7_DAYS` or `LAST_MONTH`. Ignored when `start_date` and `end_date` are both set.","default":"LAST_30_DAYS","title":"Date Range"},"description":"A preset window, for example `LAST_7_DAYS` or `LAST_MONTH`. Ignored when `start_date` and `end_date` are both set.","example":"LAST_30_DAYS"},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"First day of an explicit window, as `YYYY-MM-DD`. Requires `end_date`.","title":"Start Date"},"description":"First day of an explicit window, as `YYYY-MM-DD`. Requires `end_date`.","example":"2026-08-01"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Last day of an explicit window, as `YYYY-MM-DD`. Requires `start_date`.","title":"End Date"},"description":"Last day of an explicit window, as `YYYY-MM-DD`. Requires `start_date`.","example":"2026-08-31"}],"responses":{"200":{"description":"Up to 100 search terms, highest impressions first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SearchTermRow"},"title":"SearchTerms"}}}},"400":{"description":"`date_range` is not one of the supported values, only one of `start_date` / `end_date` was sent, a date is not `YYYY-MM-DD`, or `start_date` is after `end_date`."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."}}}},"/api/apps/{app_id}/google-ads/analytics/asset-performance":{"get":{"summary":"List Google Ads asset performance","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns every serving creative asset in the account's Performance Max campaigns, with its status, any policy limits, and its traffic over the trailing 30 days.\n\nThe 30-day window is fixed and does not follow a date parameter.\n\nGoogle attributes Performance Max metrics per served asset, so these numbers do not add up to the asset group's or campaign's totals. Use them to rank creatives against each other, not to reconcile spend.\n\nThree kinds of row arrive in one list. Assets inside an asset group carry the full shape. Logos attach to the campaign instead, so they have no asset group, no policy topics and no metrics at all. A campaign Google has not echoed back yet contributes rows built from the creative Base44 sent at launch, with a `primary_status` of `GATHERING_DATA`, an empty `performance_label` and no metrics.\n\n<Note>Performance Max campaigns only. An account without one returns an empty list.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"get_asset_performance_api_apps__app_id__google_ads_analytics_asset_performance_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads reporting you want to read.","title":"App Id"},"description":"ID of the app whose Google Ads reporting you want to read.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"One entry per serving asset, grouped by its role.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetPerformanceRow"},"title":"AssetPerformance"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."}}}},"/api/apps/{app_id}/google-ads/analytics/asset-group-top-combinations":{"get":{"summary":"List Google Ads top asset combinations","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the asset combinations Google Ads served most often in the account's Performance Max campaigns, so you can see which creative it actually pairs together.\n\nAt most 100 combinations come back and there is no paging.\n\nThe read is best-effort: when Google Ads is unavailable you get an empty list under a 200 rather than an error, so an empty list does not prove the account has no combinations.\n\n<Note>Performance Max campaigns only. An account without one returns an empty list.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"get_asset_group_top_combinations_api_apps__app_id__google_ads_analytics_asset_group_top_combinations_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads reporting you want to read.","title":"App Id"},"description":"ID of the app whose Google Ads reporting you want to read.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Up to 100 combinations.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetGroupCombinationRow"},"title":"AssetCombinations"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."}}}},"/api/apps/{app_id}/google-ads/billing/invoices":{"get":{"summary":"List Google Ads invoices","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the billing invoices for the app's Google Ads account, newest first.\n\nEach invoice covers a period and carries the ad spend, the service fee, the tax and the total, all in micros of the invoice currency. `billing_type` says why it was raised: on the weekly cycle, because spend crossed the account's threshold, or as a reconciliation of an earlier period.\n\n<Warning>A refund appears here as a row with `is_refund: true`, and its `total_micros` is a positive magnitude like any other row. Use `signed_total_micros`, which is negative on a refund, whenever you total an account's billing. Summing `total_micros` counts a refund as a charge.</Warning>\n\nAn invoice Base44 is still reconciling locally is left out until it settles, so a period can be missing for a short while after it ends.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"list_invoices_api_apps__app_id__google_ads_billing_invoices_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GoogleAdsInvoiceSummary"},"title":"Response 200 List Invoices Api Apps  App Id  Google Ads Billing Invoices Get"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."}}}},"/api/apps/{app_id}/google-ads/billing/credits":{"get":{"summary":"List Google Ads promotional credits","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the promotional credits on the app's Google Ads account.\n\nEach credit carries its face value in `amount_micros` and how much has been spent in `used_micros`, both in micros of the credit currency. Subtract one from the other for what is left.\n\nBy default only credits that can still be spent are returned. Pass `include_settled=true` to also get recently used or expired ones, which come back with `spendable: false`. If you sum credits, filter on `spendable` rather than adding every row.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"list_credits_api_apps__app_id__google_ads_billing_credits_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"include_settled","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Settled"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PromotionalCreditSummary"},"title":"Response 200 List Credits Api Apps  App Id  Google Ads Billing Credits Get"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/conversion-actions":{"get":{"summary":"List Google Ads conversion actions","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the conversion goals Base44 knows about on the app's Google Ads account.\n\nThese are Google's own goals, created when the app's conversion tracking was set up. They are read only, and match on `category` to tell which goal covers which kind of conversion. [Get conversion stats](/api-reference/get-google-ads-conversion-stats) reports what they have recorded.\n\n`conversion_id` and `conversion_label` are empty until Google publishes the goal's tag, which takes a few minutes after the goal is created.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"list_conversion_actions_api_apps__app_id__google_ads_conversion_actions_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads conversion tracking you want to manage.","title":"App Id"},"description":"ID of the app whose Google Ads conversion tracking you want to manage.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The account's conversion goals.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ConversionActionResource"},"title":"ConversionActions"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."}}}},"/api/apps/{app_id}/google-ads/campaigns/{campaign_id}/keyword-theme-suggestions":{"get":{"summary":"Suggest keyword themes for a Google Ads campaign","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSuggests keyword themes for one campaign, based on its landing page, locations and language.\n\nThis is deliberately separate from [Get campaign targeting](/api-reference/get-google-ads-campaign-targeting): suggestions are optional, so a slow or failing suggester never blocks the editor from loading what the campaign actually targets today.\n\nRead `source` to know what you got. `generated` themes come from a language model and only appear for apps that have that turned on, so the same campaign can return `google` for one caller and `generated` for another. An empty `themes` list always reports `source` as `google`, whatever the real reason, so treat that combination as \"no suggestions right now\" rather than as a statement about Google.\n\nThemes are suggestions. Anything the caller types is still valid to save through [Update campaign](/api-reference/update-google-ads-campaign).\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"get_campaign_keyword_theme_suggestions_api_apps__app_id__google_ads_campaigns__campaign_id__keyword_theme_suggestions_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","title":"Campaign Id"},"description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","example":"68b1c0d4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Suggested themes and where they came from.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsKeywordThemeSuggestions"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or there is no campaign with this ID."}}}},"/api/apps/{app_id}/google-ads/campaigns/{campaign_id}/targeting":{"get":{"summary":"Get Google Ads campaign targeting","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReads a campaign's current targeting from Google Ads: the themes it matches, the terms it excludes, and the locations on each side.\n\nThis reads Google directly rather than Base44's copy, so it is the authoritative set to seed an editor from. Send changes back with [Update campaign](/api-reference/update-google-ads-campaign).\n\nLocation entries carry a camel-cased `resourceName`, unlike the rest of this API. A location saved before Base44 recorded display names comes back with its numeric ID as `name`; nothing repairs that later, so resolve the label through [Search locations](/api-reference/search-google-ads-locations) if you need to show it.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"get_campaign_targeting_api_apps__app_id__google_ads_campaigns__campaign_id__targeting_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads campaigns to manage.","title":"App Id"},"description":"ID of the app whose Google Ads campaigns to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","title":"Campaign Id"},"description":"Base44's ID for the campaign, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns). This is not the Google Ads campaign ID, which is reported separately as `google_campaign_id`.","example":"68b1c0d4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The campaign's live targeting.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsCampaignTargeting"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or there is no campaign with this ID."}}}},"/api/apps/{app_id}/google-ads/conversions/upload":{"post":{"summary":"Upload Google Ads conversions","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReports conversions to Google Ads from your own server, for the conversions only your server knows about.\n\nThis is the counterpart to the browser-side `gtag('event', 'conversion', ...)` call. Use it for a payment that actually settled, an order confirmed after review, or anything an ad blocker would stop the browser reporting. Each conversion needs the goal it counts towards, when it happened, and the click it came from.\n\nRead the outcome from the counts rather than from `status`. A partial batch still comes back as `success`, so compare `total_uploaded` with `total_submitted`. The difference is accounted for by `duplicates_removed`, `cross_request_duplicates`, and `skipped_no_click_id`, and anything left over is rows Google rejected one by one. Google does not tell Base44 which rows those were, so the response cannot name them.\n\nSend `order_id` on every conversion you can. Base44 remembers each conversion it has reported and never reports the same one twice, keyed on the order or click ID together with the goal and the timestamp, and that record does not expire. Sending a corrected value for a conversion you already reported has no effect for that reason. Use [Upload enhanced conversions](/api-reference/upload-enhanced-google-ads-conversions) to add buyer details to a conversion you have already reported.\n\n<Note>There is no cap on how many rows you can send and no rate limit on this endpoint. Base44 sends them to Google in batches of 2,000, one batch after another, while your request stays open, so a very large list means a very long request. Keep each call to a few thousand rows and send several calls instead.</Note>\n\n<Note>A 409 means the request to Google timed out after it was sent, so those conversions may or may not have landed. Retrying is safe. Base44 has already recorded them as sent, so the retry reports them under `cross_request_duplicates` rather than counting them twice.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"upload_conversions_api_apps__app_id__google_ads_conversions_upload_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads conversion tracking you want to manage.","title":"App Id"},"description":"ID of the app whose Google Ads conversion tracking you want to manage.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"UploadConversions","type":"object","properties":{"conversions":{"type":"array","description":"The conversions to report. An empty list is accepted and reports nothing.","items":{"type":"object","required":["conversion_action_id","conversion_date_time"],"properties":{"conversion_action_id":{"type":"string","description":"Google Ads' own ID for the goal this conversion counts towards, as returned in `google_action_id` by [List conversion actions](/api-reference/list-google-ads-conversion-actions).","example":"7654321"},"conversion_date_time":{"type":"string","description":"When the conversion happened, as `YYYY-MM-DD HH:MM:SS+HH:MM` including the offset. Google rejects any other format with a 400, and Base44 passes the value through unchanged.","example":"2026-08-25 14:05:00+00:00"},"gclid":{"type":"string","description":"The Google click ID the conversion is attributed to, taken from the `gclid` query parameter on the landing URL.","example":"Cj0KCQjw1..."},"gbraid":{"type":"string","description":"The click ID for an iOS app-to-web journey, used instead of `gclid` when the landing URL carries `gbraid`.","example":"0AAAAAo1bC2d3E4f"},"wbraid":{"type":"string","description":"The click ID for a web-to-app journey, used instead of `gclid` when the landing URL carries `wbraid`.","example":"CjwKCAj0bC2d3E4f"},"conversion_value":{"type":"number","description":"What the conversion was worth, in the currency given by `currency_code`. It must be zero or more.","default":0,"example":89.9},"currency_code":{"type":"string","description":"Currency of `conversion_value` as a three-letter ISO 4217 code. Base44 uppercases it before sending, so `eur` and `EUR` both work.","default":"USD","example":"EUR"},"order_id":{"type":"string","description":"Your own ID for the order behind the conversion. Send it whenever you have one, because it is what lets Google dedupe the conversion on its side as well.","example":"ORD-10482"},"user_identifiers":{"type":"object","description":"Hashed details of the buyer, which improve how well Google matches the conversion to a click. Hash every value yourself, because Base44 sends them as given.","properties":{"hashed_email":{"type":"string","description":"The buyer's email address, lowercased, trimmed, and SHA-256 hashed to lowercase hex.","example":"d5b8e2b9a1c4f60e7a2f3c8d91b45e6f70a1c2d3e4f5061728394a5b6c7d8e9f"},"hashed_phone_number":{"type":"string","description":"The buyer's phone number in E.164 form, SHA-256 hashed to lowercase hex.","example":"9f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a39281706f5e4d3c2b1a0"},"hashed_first_name":{"type":"string","description":"The buyer's first name, lowercased and SHA-256 hashed to lowercase hex. Base44 only sends it together with `hashed_last_name`, so one without the other is dropped and has no effect.","example":"3c2b1a09f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a39281706f5e4d"},"hashed_last_name":{"type":"string","description":"The buyer's last name, lowercased and SHA-256 hashed to lowercase hex. Send it together with `hashed_first_name`, because one without the other is dropped.","example":"706f5e4d3c2b1a09f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a392817"}}}}},"example":[{"conversion_action_id":"7654321","conversion_date_time":"2026-08-25 14:05:00+00:00","gclid":"Cj0KCQjw1...","conversion_value":89.9,"currency_code":"EUR","order_id":"ORD-10482"}]}}},"example":{"conversions":[{"conversion_action_id":"7654321","conversion_date_time":"2026-08-25 14:05:00+00:00","gclid":"Cj0KCQjw1...","conversion_value":89.9,"currency_code":"EUR","order_id":"ORD-10482"}]}}}},"responses":{"200":{"description":"What happened to each conversion in the batch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversionUploadResult"}}}},"400":{"description":"Google Ads rejected the whole request, for example a malformed `conversion_date_time` or a `conversion_action_id` that is not on the account. The response message carries Google's reason."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."},"409":{"description":"The request to Google Ads timed out after being sent, so the conversions may or may not have landed. Retrying is safe and reports them as cross-request duplicates."},"429":{"description":"Google Ads is rate limiting the account. Retry later."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/conversions/upload-enhanced":{"post":{"summary":"Upload enhanced Google Ads conversions","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nAdds hashed buyer details to conversions you have already reported, so Google can match more of them to a click.\n\nUse it when you learn who the buyer was after the conversion was reported, for example a form submitted after checkout. Each adjustment finds its conversion by the `order_id` you sent originally, so it only works for a conversion you reported with one. It changes the details on an existing conversion and never creates a new one, so [Upload conversions](/api-reference/upload-google-ads-conversions) has to run first.\n\nHash each value yourself with SHA-256 over the lowercased, trimmed input and send lowercase hex. Base44 forwards what you send without hashing or normalizing it, so an unhashed or differently formatted value simply fails to match and reports as accepted.\n\nRead the outcome from `total_uploaded` rather than `status`, which reports `success` as soon as one adjustment lands.\n\n<Note>There is no cap on how many rows you can send and no rate limit on this endpoint. Base44 sends them to Google in batches of 2,000, one batch after another, while your request stays open, so a very large list means a very long request. Keep each call to a few thousand rows and send several calls instead.</Note>\n\n<Warning>A 409 means the request to Google timed out after it was sent, so the adjustments may or may not have landed. Unlike [Upload conversions](/api-reference/upload-google-ads-conversions), this endpoint keeps no record of what it has sent, so a retry can apply the same adjustment twice. Check the conversion in Google Ads before retrying.</Warning>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"upload_enhanced_conversion_api_apps__app_id__google_ads_conversions_upload_enhanced_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads conversion tracking you want to manage.","title":"App Id"},"description":"ID of the app whose Google Ads conversion tracking you want to manage.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"UploadEnhancedConversions","type":"object","properties":{"adjustments":{"type":"array","description":"The adjustments to apply. An empty list is accepted and changes nothing.","items":{"type":"object","required":["conversion_action_id","order_id","adjustment_date_time","user_identifiers"],"properties":{"conversion_action_id":{"type":"string","description":"Google Ads' own ID for the goal the original conversion counted towards, as returned in `google_action_id` by [List conversion actions](/api-reference/list-google-ads-conversion-actions).","example":"7654321"},"order_id":{"type":"string","description":"The `order_id` you sent on the original conversion. It is how Google finds the conversion to enhance, so an adjustment without it cannot be matched.","example":"ORD-10482"},"adjustment_date_time":{"type":"string","description":"When you are making the adjustment, as `YYYY-MM-DD HH:MM:SS+HH:MM` including the offset. Google rejects any other format with a 400.","example":"2026-08-26 09:15:00+00:00"},"user_identifiers":{"type":"object","description":"Hashed details of the buyer to add to the conversion. Hash every value yourself, because Base44 sends them as given. An adjustment that ends up with no usable identifier is dropped before Google sees it.","properties":{"hashed_email":{"type":"string","description":"The buyer's email address, lowercased, trimmed, and SHA-256 hashed to lowercase hex.","example":"d5b8e2b9a1c4f60e7a2f3c8d91b45e6f70a1c2d3e4f5061728394a5b6c7d8e9f"},"hashed_phone_number":{"type":"string","description":"The buyer's phone number in E.164 form, SHA-256 hashed to lowercase hex.","example":"9f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a39281706f5e4d3c2b1a0"},"hashed_first_name":{"type":"string","description":"The buyer's first name, lowercased and SHA-256 hashed to lowercase hex. Base44 only sends it together with `hashed_last_name`, so one without the other is dropped and has no effect.","example":"3c2b1a09f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a39281706f5e4d"},"hashed_last_name":{"type":"string","description":"The buyer's last name, lowercased and SHA-256 hashed to lowercase hex. Send it together with `hashed_first_name`, because one without the other is dropped.","example":"706f5e4d3c2b1a09f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a392817"}}},"user_agent":{"type":"string","description":"The browser user agent from the original conversion, which helps Google match it. Leave it out when you do not have it.","example":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"}}},"example":[{"conversion_action_id":"7654321","order_id":"ORD-10482","adjustment_date_time":"2026-08-26 09:15:00+00:00","user_identifiers":{"hashed_email":"d5b8e2b9a1c4f60e7a2f3c8d91b45e6f70a1c2d3e4f5061728394a5b6c7d8e9f"}}]}}},"example":{"adjustments":[{"conversion_action_id":"7654321","order_id":"ORD-10482","adjustment_date_time":"2026-08-26 09:15:00+00:00","user_identifiers":{"hashed_email":"d5b8e2b9a1c4f60e7a2f3c8d91b45e6f70a1c2d3e4f5061728394a5b6c7d8e9f"}}]}}}},"responses":{"200":{"description":"What happened to the batch of adjustments.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnhancedConversionUploadResult"}}}},"400":{"description":"Google Ads rejected the whole request, for example a malformed `conversion_date_time` or a `conversion_action_id` that is not on the account. The response message carries Google's reason."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."},"409":{"description":"The request to Google Ads timed out after being sent, so the adjustments may or may not have landed. Check the conversion in Google Ads before retrying, because a retry can apply the same adjustment twice."},"429":{"description":"Google Ads is rate limiting the account. Retry later."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/conversions/stats":{"get":{"summary":"Get Google Ads conversion stats","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns conversions and conversion value per campaign for a date window.\n\nOnly campaigns with activity in the window are returned, and archived campaigns are left out. An account with no synced campaigns returns an empty list.\n\n<Note>These numbers come from Base44's own nightly copy of your Google Ads metrics, not from Google directly, so the last few hours of activity can be missing.</Note>\n\n<Note>`start_date` and `end_date` are inclusive and must be `YYYY-MM-DD`. This endpoint does not reject a malformed date: it compares the value as text, so a date in another format silently matches the wrong days.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"get_conversion_stats_api_apps__app_id__google_ads_conversions_stats_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads conversion tracking you want to manage.","title":"App Id"},"description":"ID of the app whose Google Ads conversion tracking you want to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"start_date","in":"query","required":true,"schema":{"type":"string","description":"First day of the window, as `YYYY-MM-DD`. Inclusive.","title":"Start Date"},"description":"First day of the window, as `YYYY-MM-DD`. Inclusive.","example":"2026-08-01"},{"name":"end_date","in":"query","required":true,"schema":{"type":"string","description":"Last day of the window, as `YYYY-MM-DD`. Inclusive.","title":"End Date"},"description":"Last day of the window, as `YYYY-MM-DD`. Inclusive.","example":"2026-08-31"}],"responses":{"200":{"description":"One entry per campaign with conversions in the window.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ConversionStatRow"},"title":"ConversionStats"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/conversion-mappings":{"get":{"summary":"List Google Ads conversion mappings","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns every conversion mapping on one of the app's Google Ads accounts.\n\nA conversion mapping records that one of the account's Google Ads goals belongs to an entity write in the app, so `Order.create` is the app event behind the `PURCHASE` goal. Base44 reads mappings to decide whether a campaign is ready to launch, and to offer goals in the dashboard.\n\nPass the account in the `account_id` query parameter. An account that belongs to another app returns a 404 even when it sits in the same workspace.\n\n<Warning>A mapping does not report conversions on its own. Nothing fires a conversion when the mapped entity is written. The app has to fire it in the browser with `gtag('event', 'conversion', ...)`, or you report it from the server with [Upload conversions](/api-reference/upload-google-ads-conversions).</Warning>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"list_conversion_mappings_api_apps__app_id__google_ads_conversion_mappings_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads conversion tracking you want to manage.","title":"App Id"},"description":"ID of the app whose Google Ads conversion tracking you want to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"account_id","in":"query","required":true,"schema":{"type":"string","description":"ID of the Google Ads account the mapping belongs to, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account belonging to a different app returns a 404.","title":"Account Id"},"description":"ID of the Google Ads account the mapping belongs to, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account belonging to a different app returns a 404.","example":"68b1c0d4e7b91d003c45a1f8"}],"responses":{"200":{"description":"The account's conversion mappings.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ConversionMappingResource"},"title":"ConversionMappings"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The account does not belong to this app."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Create Google Ads conversion mapping","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nCreates a conversion mapping on one of the app's Google Ads accounts.\n\nA conversion mapping records that one of the account's Google Ads goals belongs to an entity write in the app, so `Order.create` is the app event behind the `PURCHASE` goal. Base44 reads mappings to decide whether a campaign is ready to launch, and to offer goals in the dashboard.\n\nPoint the mapping at a goal in one of two ways. Set `conversion_action_id` to name a specific goal, or set `conversion_type` to match whichever goal on the account carries that category. Setting both means the explicit ID wins. Pass the account in the `account_id` query parameter, and an account belonging to another app returns a 404 even when it sits in the same workspace.\n\nAn enabled mapping that resolves to a real goal is what makes Base44 treat the account's conversion events as configured, which affects [Get launch readiness](/api-reference/get-google-ads-launch-readiness). Send `is_enabled` as `false` to record the mapping without that effect.\n\n<Warning>This creates a new mapping every time you call it. Nothing keeps one mapping per entity and goal, so a retried request leaves you with duplicates. Read [List conversion mappings](/api-reference/list-google-ads-conversion-mappings) first and use [Update conversion mapping](/api-reference/update-google-ads-conversion-mapping) when the mapping already exists.</Warning>\n\n<Warning>A mapping does not report conversions on its own. Nothing fires a conversion when the mapped entity is written. The app has to fire it in the browser with `gtag('event', 'conversion', ...)`, or you report it from the server with [Upload conversions](/api-reference/upload-google-ads-conversions).</Warning>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"create_conversion_mapping_api_apps__app_id__google_ads_conversion_mappings_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads conversion tracking you want to manage.","title":"App Id"},"description":"ID of the app whose Google Ads conversion tracking you want to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"account_id","in":"query","required":true,"schema":{"type":"string","description":"ID of the Google Ads account the mapping belongs to, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account belonging to a different app returns a 404.","title":"Account Id"},"description":"ID of the Google Ads account the mapping belongs to, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). An account belonging to a different app returns a 404.","example":"68b1c0d4e7b91d003c45a1f8"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"CreateConversionMapping","type":"object","required":["entity_name"],"properties":{"entity_name":{"type":"string","description":"Name of the app entity whose write this goal belongs to.","example":"Order"},"trigger_action":{"type":"string","description":"Which write on the entity the goal belongs to. Base44 uses `create`, `update`, and `delete`, and stores any other value as sent.","default":"create","example":"create"},"conversion_type":{"type":"string","description":"Which kind of conversion this is, using Google's category vocabulary such as `PURCHASE`, `ADD_TO_CART`, `SIGN_UP`, or `LEAD`. The value is stored as sent and not checked against Google's list.","example":"PURCHASE"},"conversion_action_id":{"type":"string","description":"Google Ads' own ID for the goal to point at, as returned in `google_action_id` by [List conversion actions](/api-reference/list-google-ads-conversion-actions). Send it alongside `conversion_type` and this one is matched first, with `conversion_type` as the fallback if it matches no goal on the account.","example":"7654321"},"value_field":{"type":"string","description":"Field on the entity record holding the conversion amount. The wiring instructions fall back to `amount` when it is empty.","example":"total_price"},"currency_field":{"type":"string","description":"Field on the entity record holding the currency code. The wiring instructions fall back to `USD` when it is empty.","example":"currency"},"id_field":{"type":"string","description":"Field on the entity record holding the order ID Google dedupes on. The wiring instructions fall back to `id` when it is empty.","example":"order_number"},"is_enabled":{"type":"boolean","description":"Whether Base44 counts this mapping when it decides the account's conversion events are configured (`true`) or ignores it (`false`).","default":true,"example":true}}},"example":{"entity_name":"Order","trigger_action":"create","conversion_type":"PURCHASE","value_field":"total_price","currency_field":"currency","id_field":"order_number"}}}},"responses":{"200":{"description":"The mapping that was created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversionMappingResource"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The account does not belong to this app."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/conversion-mappings/{mapping_id}":{"patch":{"summary":"Update Google Ads conversion mapping","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nChanges a conversion mapping.\n\nSend only the fields you want to change. A field you leave out keeps its current value, and so does a field you send as `null`, so clear a field by sending an empty string rather than `null`. There is no `account_id` parameter here, because the mapping already knows which account it belongs to.\n\nTurning `is_enabled` off is how you stop Base44 counting the mapping when it decides the account's conversion events are configured, without losing the mapping itself.\n\n<Warning>A mapping does not report conversions on its own. Nothing fires a conversion when the mapped entity is written. The app has to fire it in the browser with `gtag('event', 'conversion', ...)`, or you report it from the server with [Upload conversions](/api-reference/upload-google-ads-conversions).</Warning>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"update_conversion_mapping_api_apps__app_id__google_ads_conversion_mappings__mapping_id__patch","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads conversion tracking you want to manage.","title":"App Id"},"description":"ID of the app whose Google Ads conversion tracking you want to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"mapping_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the mapping, as returned in `id` by [List conversion mappings](/api-reference/list-google-ads-conversion-mappings).","title":"Mapping Id"},"description":"ID of the mapping, as returned in `id` by [List conversion mappings](/api-reference/list-google-ads-conversion-mappings).","example":"68b1c0d4e7b91d003c45a1f7"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"UpdateConversionMapping","type":"object","properties":{"entity_name":{"type":"string","description":"Name of the app entity whose write this goal belongs to.","example":"Order"},"trigger_action":{"type":"string","description":"Which write on the entity the goal belongs to. Base44 uses `create`, `update`, and `delete`, and stores any other value as sent.","default":"create","example":"create"},"conversion_type":{"type":"string","description":"Which kind of conversion this is, using Google's category vocabulary such as `PURCHASE`, `ADD_TO_CART`, `SIGN_UP`, or `LEAD`. The value is stored as sent and not checked against Google's list.","example":"PURCHASE"},"conversion_action_id":{"type":"string","description":"Google Ads' own ID for the goal to point at, as returned in `google_action_id` by [List conversion actions](/api-reference/list-google-ads-conversion-actions). Send it alongside `conversion_type` and this one is matched first, with `conversion_type` as the fallback if it matches no goal on the account.","example":"7654321"},"value_field":{"type":"string","description":"Field on the entity record holding the conversion amount. The wiring instructions fall back to `amount` when it is empty.","example":"total_price"},"currency_field":{"type":"string","description":"Field on the entity record holding the currency code. The wiring instructions fall back to `USD` when it is empty.","example":"currency"},"id_field":{"type":"string","description":"Field on the entity record holding the order ID Google dedupes on. The wiring instructions fall back to `id` when it is empty.","example":"order_number"},"is_enabled":{"type":"boolean","description":"Whether Base44 counts this mapping when it decides the account's conversion events are configured (`true`) or ignores it (`false`).","default":true,"example":true}}},"example":{"value_field":"grand_total","is_enabled":true}}}},"responses":{"200":{"description":"The mapping after the change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversionMappingResource"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The account does not belong to this app, or there is no mapping with this ID in your workspace."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete Google Ads conversion mapping","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nDeletes a conversion mapping.\n\nThe Google Ads goal the mapping pointed at is untouched. Only the record tying that goal to an entity write goes away, so nothing about the account's conversion goals changes and you can recreate the mapping with [Create conversion mapping](/api-reference/create-google-ads-conversion-mapping).\n\nDeleting the last enabled mapping on an account stops Base44 treating its conversion events as configured, which [Get launch readiness](/api-reference/get-google-ads-launch-readiness) reflects. Send `is_enabled` as `false` through [Update conversion mapping](/api-reference/update-google-ads-conversion-mapping) instead when you want to keep the record.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"delete_conversion_mapping_api_apps__app_id__google_ads_conversion_mappings__mapping_id__delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads conversion tracking you want to manage.","title":"App Id"},"description":"ID of the app whose Google Ads conversion tracking you want to manage.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"mapping_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the mapping, as returned in `id` by [List conversion mappings](/api-reference/list-google-ads-conversion-mappings).","title":"Mapping Id"},"description":"ID of the mapping, as returned in `id` by [List conversion mappings](/api-reference/list-google-ads-conversion-mappings).","example":"68b1c0d4e7b91d003c45a1f7"}],"responses":{"200":{"description":"The mapping was deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversionMappingDeleteResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The account does not belong to this app, or there is no mapping with this ID in your workspace."}}}},"/api/apps/{app_id}/google-ads/assets/generate-text":{"post":{"summary":"Generate Google Ads text assets","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nWrites ad copy for the app and returns it as previews.\n\nNothing reaches Google Ads here. Each line comes back with a `session_asset_id` you pass to [Accept a generated Google Ads asset](/api-reference/accept-a-generated-google-ads-asset) to put it on a live campaign. Use [Generate Google Ads assets](/api-reference/generate-google-ads-assets) instead when you want copy and pictures in one call.\n\nPick the lines you want with `asset_field_types`. `SEARCH` campaigns do not take `LONG_HEADLINE`, so Google rejects that combination.\n\nGrounding is flexible here, unlike image generation. You may send `final_url` and `freeform_prompt` together, and sending neither grounds the copy in the app's own home page.\n\nThe copy is written in the language of the app's own site, not the language you ask in, and the response reports which one in `language`.\n\n<Note>Only one generation of each kind runs per app at a time. Calling this while one is running returns a 200 carrying `in_flight` set to `true`, that run's `session_id`, and an empty `assets`. Poll the `session_id` you were given rather than retrying.</Note>\n\nThis endpoint costs 2 of 20 copy requests a minute per app, shared with the other Google Ads endpoints that write copy, and up to 4 when the retry below happens.\n\n<Note>When Google refuses the page you pointed at, Base44 retries once on the app's own published URL, and that retry costs the same again. Pace requests against the doubled figure, not the single one, or a refusal-heavy app hits a 429 part-way through its own recovery.</Note>\n\n<Note>Generating creative does not spend credits and is not billed. It is capped by rate limits instead, so a burst gets a 429 rather than a bill.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"generate_text_assets_api_apps__app_id__google_ads_assets_generate_text_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads creative you want to generate or read.","title":"App Id"},"description":"ID of the app whose Google Ads creative you want to generate or read.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"GenerateTextAssets","type":"object","properties":{"channel_type":{"type":"string","description":"Campaign type the copy is for, one of `PERFORMANCE_MAX`, `DEMAND_GEN`, `SEARCH` or `DISPLAY`.","default":"PERFORMANCE_MAX","example":"PERFORMANCE_MAX"},"asset_field_types":{"type":"array","description":"Which kinds of line to write, from `HEADLINE`, `LONG_HEADLINE` or `DESCRIPTION`. Send between one and three entries. An image field type here is rejected with a 422.","items":{"type":"string","description":"One of `HEADLINE`, `LONG_HEADLINE` or `DESCRIPTION`.","example":"HEADLINE"},"default":["HEADLINE","DESCRIPTION"],"example":["HEADLINE","DESCRIPTION"]},"final_url":{"type":"string","description":"Page to base the copy on. Unlike image generation you may send this together with `freeform_prompt`, and sending neither grounds the copy in the app's own home page.","example":"https://example.com/spring"},"freeform_prompt":{"type":"string","description":"What the copy should say, in your own words, up to 1500 characters.","example":"Lead with free delivery across Germany"},"keywords":{"type":"array","description":"Keywords to work into the copy. Send between 1 and 15 of them, and none of them blank. Omit the field entirely rather than sending an empty list.","items":{"type":"string","description":"One keyword. It must not be blank or whitespace only.","example":"oak furniture"},"minItems":1,"maxItems":15,"example":["oak furniture","handmade table"]}}},"example":{"channel_type":"PERFORMANCE_MAX","asset_field_types":["HEADLINE","DESCRIPTION"],"final_url":"https://example.com/spring"}}}},"responses":{"200":{"description":"The copy that was generated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateTextAssetsResult"}}}},"400":{"description":"Google Ads rejected the generation request, for example a landing page it could not read. The response message carries Google's reason."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"409":{"description":"The request to Google Ads timed out after being sent. Nothing was attached to a campaign, so retrying is safe."},"422":{"description":"`asset_field_types` contains a value that is not a text field type, is empty, or has more than three entries; `keywords` is present but empty, longer than 15 entries, or contains a blank entry; `freeform_prompt` is longer than 1500 characters; or the channel is `SEARCH` and you asked for `LONG_HEADLINE`."},"429":{"description":"The app has used up one of the creative rate limits, either 20 copy requests a minute or 10 picture requests a minute. Retry later."}}}},"/api/apps/{app_id}/google-ads/assets/generate-images":{"post":{"summary":"Generate Google Ads image assets","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nGenerates ad pictures for the app and returns them as previews.\n\nNothing reaches Google Ads here. Each picture comes back with a `session_asset_id` you pass to [Accept a generated Google Ads asset](/api-reference/accept-a-generated-google-ads-asset) to put it on a live campaign.\n\nSend exactly one of `final_url` and `freeform_prompt`. Sending both is rejected, and so is sending neither, which is the one place this endpoint differs from [Generate Google Ads assets](/api-reference/generate-google-ads-assets), where omitting both is allowed and grounds the pictures in the app's home page.\n\nPictures are slower than copy, so the response returns once the first one is ready. `assets` holds what finished in time and `background_pending` with `background_count` say that more are coming. Poll [List Google Ads generated assets](/api-reference/list-google-ads-generated-assets) with the returned `session_id` for the rest. `assets` can be empty while `background_pending` is `true`, which means the pictures are being generated but none landed in time.\n\n<Note>Only one generation of each kind runs per app at a time. Calling this while one is running returns a 200 carrying `in_flight` set to `true`, that run's `session_id`, and an empty `assets`. Poll the `session_id` you were given rather than retrying.</Note>\n\nEach shape in `asset_field_types` costs one of 10 picture requests a minute per app, and the retry below costs the whole shape count again. So four shapes cost 4 normally and 8 on a retry, which is one call a minute in the worst case rather than two, and a single shape costs 1 or 2.\n\n<Note>When Google refuses the page you pointed at, Base44 retries once on the app's own published URL, and that retry costs the same again. Pace requests against the doubled figure, not the single one, or a refusal-heavy app hits a 429 part-way through its own recovery.</Note>\n\n<Note>Generating creative does not spend credits and is not billed. It is capped by rate limits instead, so a burst gets a 429 rather than a bill.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"generate_image_assets_api_apps__app_id__google_ads_assets_generate_images_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads creative you want to generate or read.","title":"App Id"},"description":"ID of the app whose Google Ads creative you want to generate or read.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"GenerateImageAssets","type":"object","properties":{"channel_type":{"type":"string","description":"Campaign type the pictures are for, one of `PERFORMANCE_MAX`, `DEMAND_GEN`, `SEARCH` or `DISPLAY`. It does not restrict the shapes you may ask for.","default":"PERFORMANCE_MAX","example":"PERFORMANCE_MAX"},"asset_field_types":{"type":"array","description":"Which picture shapes to generate, from `MARKETING_IMAGE`, `SQUARE_MARKETING_IMAGE`, `PORTRAIT_MARKETING_IMAGE` or `TALL_PORTRAIT_MARKETING_IMAGE`. Send between one and four entries. Each shape costs one request against the picture rate limit, so asking for fewer gets you more calls a minute. A text field type here is rejected with a 422.","items":{"type":"string","description":"One of `MARKETING_IMAGE`, `SQUARE_MARKETING_IMAGE`, `PORTRAIT_MARKETING_IMAGE` or `TALL_PORTRAIT_MARKETING_IMAGE`.","example":"SQUARE_MARKETING_IMAGE"},"default":["MARKETING_IMAGE","SQUARE_MARKETING_IMAGE","PORTRAIT_MARKETING_IMAGE","TALL_PORTRAIT_MARKETING_IMAGE"],"example":["MARKETING_IMAGE","SQUARE_MARKETING_IMAGE"]},"final_url":{"type":"string","description":"Page to base the pictures on. Send exactly one of this and `freeform_prompt`.","example":"https://example.com/spring"},"freeform_prompt":{"type":"string","description":"What the pictures should show, in your own words, up to 1500 characters. Send exactly one of this and `final_url`.","example":"Warm studio shots of handmade oak dining tables"}}},"example":{"asset_field_types":["MARKETING_IMAGE","SQUARE_MARKETING_IMAGE"],"final_url":"https://example.com/spring"}}}},"responses":{"200":{"description":"The pictures that were generated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateImageAssetsResult"}}}},"400":{"description":"Google Ads rejected the generation request, for example a landing page it could not read. The response message carries Google's reason."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"409":{"description":"The request to Google Ads timed out after being sent. Nothing was attached to a campaign, so retrying is safe."},"422":{"description":"You sent both `final_url` and `freeform_prompt` or neither, or `asset_field_types` contains a value that is not an image field type, is empty, or has more than four entries, or `freeform_prompt` is longer than 1500 characters."},"429":{"description":"The app has used up one of the creative rate limits, either 20 copy requests a minute or 10 picture requests a minute. Retry later."}}}},"/api/apps/{app_id}/google-ads/assets/generate-all":{"post":{"summary":"Generate Google Ads assets","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nWrites ad copy and generates pictures for the app in one call, and returns them as previews.\n\nNothing reaches Google Ads here. Everything comes back as a preview you look at first, and only [Accept a generated Google Ads asset](/api-reference/accept-a-generated-google-ads-asset) puts one on a live campaign.\n\nWhich slots get filled follows `channel_type`. `SEARCH` gets `HEADLINE` and `DESCRIPTION` copy plus landscape and square pictures. `DISPLAY` gets the same pictures and adds `LONG_HEADLINE` copy. `PERFORMANCE_MAX` and `DEMAND_GEN` get that copy plus all four picture shapes, adding portrait and tall portrait. You cannot ask for individual slots on this endpoint.\n\nThe copy is written in the language of the app's own site, not the language you ask in, and the response reports which one in `language`.\n\nPictures take longer than copy, so the response returns as soon as the copy is ready. `images` holds only the pictures that finished in time and `background_image_count` says how many more are coming. Poll [List Google Ads generated assets](/api-reference/list-google-ads-generated-assets) with the returned `session_id` for the rest.\n\n<Warning>Only one generation runs per app at a time. Calling this while one is already running returns a 200 carrying **that** run's `session_id` with `text` and `images` empty, and with `background_image_count` and `language` absent. That absence is how you tell it apart from a generation that genuinely produced nothing, and the right response is to poll the `session_id` you were given rather than to retry.</Warning>\n\nRate limits are per app and split across two pools. The copy costs 2 of 20 requests a minute. The pictures cost one of 10 requests a minute for each picture shape, so a `PERFORMANCE_MAX` call costs 4 and a `SEARCH` call costs 2. The picture pool runs out first, which puts a `PERFORMANCE_MAX` ceiling of about two calls a minute. A leg that has nothing to do costs nothing. Each leg can also retry once, as below, and the retry costs that leg the same again.\n\n<Note>When Google refuses the page you pointed at, Base44 retries once on the app's own published URL, and that retry costs the same again. Pace requests against the doubled figure, not the single one, or a refusal-heavy app hits a 429 part-way through its own recovery.</Note>\n\n<Note>Generating creative does not spend credits and is not billed. It is capped by rate limits instead, so a burst gets a 429 rather than a bill.</Note>\n\n<Note>When one leg fails and the other produced something, you get a 200 with only the successful leg's assets. The failure is only raised when neither leg produced anything, so check `text` and `images` rather than assuming a 200 means both ran.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"generate_all_assets_api_apps__app_id__google_ads_assets_generate_all_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads creative you want to generate or read.","title":"App Id"},"description":"ID of the app whose Google Ads creative you want to generate or read.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"GenerateAssets","type":"object","properties":{"channel_type":{"type":"string","description":"Campaign type to generate for, which decides the slots that get filled. One of `PERFORMANCE_MAX`, `DEMAND_GEN`, `SEARCH` or `DISPLAY`.","default":"PERFORMANCE_MAX","example":"PERFORMANCE_MAX"},"final_url":{"type":"string","description":"Page the pictures should be based on. Send this or `freeform_prompt`, not both.","example":"https://example.com/spring"},"freeform_prompt":{"type":"string","description":"What the pictures should show, in your own words, up to 1500 characters. Send this or `final_url`, not both.","example":"Warm studio shots of handmade oak dining tables"}}},"example":{"channel_type":"PERFORMANCE_MAX","final_url":"https://example.com/spring"}}}},"responses":{"200":{"description":"The creative that was generated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateAssetsResult"}}}},"400":{"description":"Google Ads rejected the generation request, for example a landing page it could not read. The response message carries Google's reason."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"409":{"description":"The request to Google Ads timed out after being sent. Nothing was attached to a campaign, so retrying is safe."},"422":{"description":"You sent both `final_url` and `freeform_prompt`, or `freeform_prompt` is longer than 1500 characters."},"429":{"description":"The app has used up one of the creative rate limits, either 20 copy requests a minute or 10 picture requests a minute. Retry later."}}}},"/api/apps/{app_id}/google-ads/assets/{session_asset_id}/accept":{"post":{"summary":"Accept a generated Google Ads asset","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nPuts one generated asset onto a live Performance Max campaign.\n\nThis is the step that reaches Google Ads. Up to here creative is only a preview, and this creates the asset on the app's own Google Ads account and links it to **every** asset group the campaign has, with the slot the asset was generated for. After this Google Ads is the source of truth for it.\n\nYou can accept an asset long after the generation that produced it. Base44 looks the asset up in the app's durable library first and only falls back to the live session, so an asset you read from `library` works even though its preview has expired. `session_id` is still required, because it is what the fallback needs.\n\nPerformance Max only. Other campaign types have no asset groups, so they are rejected with a 422.\n\nA 409 covers four different situations and the message is what tells them apart, so read it rather than retrying blindly. Waiting is right for an image that is still processing, syncing is right for a campaign that has not reached Google Ads, and regenerating is right for an asset that is no longer usable.\n\n<Warning>Accepting the same picture twice creates a second asset on the campaign. Google has no content check for images, so a retried or repeated accept leaves you with duplicate creative that you have to remove in Google Ads. Copy is safe to repeat, because Google matches identical text and reuses the existing asset. Treat an image accept as a one-shot and read [List Google Ads generated assets](/api-reference/list-google-ads-generated-assets) to check `status` before retrying.</Warning>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"accept_generated_asset_api_apps__app_id__google_ads_assets__session_asset_id__accept_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads creative you want to generate or read.","title":"App Id"},"description":"ID of the app whose Google Ads creative you want to generate or read.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"session_asset_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the generated asset to attach, as returned in `session_asset_id` by [Generate Google Ads assets](/api-reference/generate-google-ads-assets) or [List Google Ads generated assets](/api-reference/list-google-ads-generated-assets).","title":"Session Asset Id"},"description":"ID of the generated asset to attach, as returned in `session_asset_id` by [Generate Google Ads assets](/api-reference/generate-google-ads-assets) or [List Google Ads generated assets](/api-reference/list-google-ads-generated-assets).","example":"b7f3a1c8-52d4-4a0e-9b31-2c6f0d8e4a19"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"AcceptGeneratedAsset","type":"object","required":["session_id","campaign_id"],"properties":{"session_id":{"type":"string","description":"The generation the asset came from, as returned in `session_id` by [Generate Google Ads assets](/api-reference/generate-google-ads-assets).","example":"9c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f"},"campaign_id":{"type":"string","description":"The Performance Max campaign to attach the asset to, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns).","example":"68b1c0d4e7b91d003c45a1f2"}}},"example":{"session_id":"9c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f","campaign_id":"68b1c0d4e7b91d003c45a1f2"}}}},"responses":{"200":{"description":"The asset was attached to the campaign.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptAssetResult"}}}},"400":{"description":"The asset could not be used as it stands: Base44 could not reload the generated image, the generated copy is empty, or Google Ads rejected the asset, for example copy that breaks its policies. Generate a replacement rather than retrying this one. A Google rejection carries its reason in the message."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or there is no campaign with this ID on it. It is also returned when there is no generated asset with this `session_asset_id`."},"409":{"description":"The accept cannot proceed yet, and the message says which of four reasons applies: the generated image is still processing, so wait and retry; the campaign has not reached Google Ads yet or has no asset groups, so sync it and retry; the asset is no longer usable, so generate a replacement; or the request to Google Ads timed out after being sent, in which case the asset may or may not have been attached and a retry can duplicate it. Read the message before choosing what to do."},"422":{"description":"The campaign is not a Performance Max campaign. Only Performance Max campaigns have the asset groups these endpoints work on."},"429":{"description":"Google Ads is rate limiting the account. Retry later."}}}},"/api/apps/{app_id}/google-ads/assets":{"get":{"summary":"List Google Ads generated assets","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the ad creative Base44 has generated for the app.\n\nWhich field you get back depends on what you ask for, and you never get both `session` and `library`:\n\n- Send `session_id` and you get `session`, the previews from that one generation. Use it to poll a generation you just started. Previews are transient, so an expired session reads as an empty list.\n- Omit `session_id` and you get `library`, the app's generated assets newest first. This is the durable copy and the one to read when you want to pick something to use. It returns at most the 5,000 most recent, with no pagination and no marker when it truncates.\n- Add `campaign_id` to either and you also get `durable`, read live from Google Ads, listing what is already attached to that campaign.\n\nThe two lists carry different fields, because they come from different stores. `library` rows add `status`, `accepted_campaign_id` and `accepted_resource_names`, which is how you tell what has already been pushed to Google.\n\nA `session` list is empty rather than missing while a generation is in flight, so poll until assets appear rather than treating the first empty read as failure.\n\nSending `campaign_id` brings the campaign's own errors with it, because the campaign has to be resolved before Google can be read. Without `campaign_id` this endpoint only ever answers 200, 401 or 403.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"list_generated_assets_api_apps__app_id__google_ads_assets_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose Google Ads creative you want to generate or read.","title":"App Id"},"description":"ID of the app whose Google Ads creative you want to generate or read.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"session_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Return the previews from this generation session instead of the durable library, as returned in `session_id` by [Generate Google Ads assets](/api-reference/generate-google-ads-assets).","title":"Session Id"},"description":"Return the previews from this generation session instead of the durable library, as returned in `session_id` by [Generate Google Ads assets](/api-reference/generate-google-ads-assets).","example":"9c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f"},{"name":"campaign_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Also return what is already attached to this campaign in Google Ads, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns).","title":"Campaign Id"},"description":"Also return what is already attached to this campaign in Google Ads, as returned in `id` by [List campaigns](/api-reference/list-google-ads-campaigns).","example":"68b1c0d4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The app's generated creative.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeneratedAssetsList"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account, or there is no campaign with this ID on it. Only returned when you send `campaign_id`."},"409":{"description":"The campaign has not reached Google Ads yet, so it has no asset groups to read or attach to. Sync it and retry. Only returned when you send `campaign_id`."},"422":{"description":"The campaign is not a Performance Max campaign. Only Performance Max campaigns have the asset groups these endpoints work on. Only returned when you send `campaign_id`."}}}},"/api/apps/{app_id}/entity-schemas":{"get":{"summary":"List entity schemas","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns every entity schema the app defines, plus the built-in `User` entity when it has custom fields.\n\nTo read one schema on its own, use [Get entity schema](/api-reference/get-entity-schema).","operationId":"list_schemas_api_apps__app_id__entity_schemas_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose entity schemas you want to work with.","title":"App Id"},"description":"ID of the app whose entity schemas you want to work with.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEntitySchemasResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or you used a workspace API key."},"404":{"description":"App not found."}}},"post":{"summary":"Create entity schema","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nAdds a new entity to the specified app.\n\nThis changes the app's live data model, so it takes effect immediately. It doesn't change the file that defines that model in the app's source code. Since Base44 rebuilds the live model whenever the file is written or the app's code is pulled from GitHub, a change made using this endpoint may be reverted.\n\nTo change the model for good, change the entities configuration files.\n\nYou can't create `User`, which is built in. Use [Update entity schema](/api-reference/update-entity-schema) with `User` to add custom fields to it.","operationId":"create_schema_api_apps__app_id__entity_schemas_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose entity schemas you want to work with.","title":"App Id"},"description":"ID of the app whose entity schemas you want to work with.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEntitySchemaRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntitySchemaResponse"}}}},"400":{"description":"The `entity_name` is empty, has characters other than letters, numbers, and underscores, or is `User`. It also fires when `entity_schema` is not a valid JSON Schema, or the schema sets row-level security rules Base44 cannot enforce."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or your workspace API key lacks the `apps:deploy` scope."},"404":{"description":"App not found."},"409":{"description":"The app already has an entity with this name (a workspace API key resending a byte-identical schema gets a 200 instead), or the request is scoped to a feature branch (entity schemas can only be changed on the main branch)."},"422":{"description":"The request body is missing, or `entity_name` or `entity_schema` is missing or has the wrong type. Whether `entity_schema` is a usable JSON Schema is checked after this and returns a 400."}}},"put":{"summary":"Sync entity schemas","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReplaces the specified app's entire set of entities with the one you send, in a single call.\n\nSend every entity the app should have. Any entity the app currently has that is missing from `entityNameToSchema` is deleted. Include `User` to keep its custom fields. Leaving it out drops them. An empty object deletes every entity the app has.\n\nBase44 won't delete an entity that still holds records. If the set you send leaves out an entity that has records, the whole call fails and nothing changes.\n\nThis changes the app's live data model, so it takes effect immediately. It doesn't change the file that defines that model in the app's source code. Since Base44 rebuilds the live model whenever the file is written or the app's code is pulled from GitHub, a change made using this endpoint may be reverted.\n\nThis endpoint requires the app's source code to be under your control. That includes projects you create with the Base44 CLI and projects you [eject](/developers/references/cli/commands/eject) from the Base44 online app editor. Some managed-source apps may also be granted access.","operationId":"sync_schemas_api_apps__app_id__entity_schemas_put","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose entity schemas you want to work with.","title":"App Id"},"description":"ID of the app whose entity schemas you want to work with.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncEntitySchemasRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncEntitySchemasResponse"}}}},"400":{"description":"An entity name is empty or has characters other than letters, numbers, and underscores. It also fires when a schema is not a valid JSON Schema, the `User` schema redeclares `email` or `full_name`, or a schema sets row-level security rules Base44 cannot enforce."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or your workspace API key lacks the `apps:deploy` scope."},"404":{"description":"App not found."},"409":{"description":"The request is scoped to a feature branch. Entity schemas can only be changed on the main branch."},"422":{"description":"The request body is missing, or `entityNameToSchema` is missing, is not an object, or maps a name to something other than an object. Whether each value is a usable JSON Schema is checked after this and returns a 400."},"428":{"description":"This endpoint isn't available for this app, because Base44 manages its source code. It also fires when an entity the sync would delete still has records."}}}},"/api/apps/{app_id}/entity-schemas/{entity_name}":{"get":{"summary":"Get entity schema","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns one entity's JSON Schema.\n\nPass `User` as the `entity_name` to read the custom fields added to the built-in user entity.","operationId":"get_schema_api_apps__app_id__entity_schemas__entity_name__get","parameters":[{"name":"entity_name","in":"path","required":true,"schema":{"type":"string","description":"Name of the entity, as returned by [List entity schemas](/api-reference/list-entity-schemas). Pass `User` for the built-in user entity.","title":"Entity Name"},"description":"Name of the entity, as returned by [List entity schemas](/api-reference/list-entity-schemas). Pass `User` for the built-in user entity.","example":"Invoice"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose entity schemas you want to work with.","title":"App Id"},"description":"ID of the app whose entity schemas you want to work with.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"EntitySchemaDocument","description":"The entity's stored JSON Schema, including its `properties`, `required` fields, and any row-level security rules under `rls`. For the app's own entities it also carries a `name` key holding the entity name. For `User` it holds only the custom fields added on top of the built-in ones, and has no `name` key."},"example":{"name":"Invoice","type":"object","properties":{"amount":{"type":"number","description":"Total amount in cents"},"status":{"type":"string","enum":["draft","sent","paid"]}},"required":["amount"],"rls":{"read":{"created_by":"{{user.email}}"}}}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or you used a workspace API key."},"404":{"description":"App not found, the app has no entity with this name, or `User` was requested and the app has no custom user fields."}}},"put":{"summary":"Update entity schema","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReplaces an entity's JSON Schema with the one you send. This is a full replacement, not a merge. Anything you leave out is dropped from the schema.\n\nThis changes the app's live data model, so it takes effect immediately. It doesn't change the file that defines that model in the app's source code. Since Base44 rebuilds the live model whenever the file is written or the app's code is pulled from GitHub, a change made using this endpoint may be reverted.\n\nTo change the model for good, change the entities configuration files.\n\nPass `User` as the `entity_name` to set custom fields on the built-in user entity. Those fields can't redeclare `email` or `full_name`, which Base44 manages.","operationId":"update_schema_api_apps__app_id__entity_schemas__entity_name__put","parameters":[{"name":"entity_name","in":"path","required":true,"schema":{"type":"string","description":"Name of the entity to replace, as returned by [List entity schemas](/api-reference/list-entity-schemas). Pass `User` to set the built-in user entity's custom fields.","title":"Entity Name"},"description":"Name of the entity to replace, as returned by [List entity schemas](/api-reference/list-entity-schemas). Pass `User` to set the built-in user entity's custom fields.","example":"Invoice"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose entity schemas you want to work with.","title":"App Id"},"description":"ID of the app whose entity schemas you want to work with.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEntitySchemaRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntitySchemaResponse"}}}},"400":{"description":"The `entity_schema` is not a valid JSON Schema, the `User` schema redeclares `email` or `full_name`, or the schema sets row-level security rules Base44 cannot enforce."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or your workspace API key lacks the `apps:deploy` scope."},"404":{"description":"App not found, or the app has no entity with this name (`User` is created instead of returning a 404)."},"409":{"description":"The request is scoped to a feature branch. Entity schemas can only be changed on the main branch."},"422":{"description":"The request body is missing, or `entity_schema` is missing or is not an object. Whether it is a usable JSON Schema is checked after this and returns a 400."}}},"delete":{"summary":"Delete entity schema","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRemoves an entity from the app.\n\nDelete the entity's records first. An entity that still has records can't be removed, and the call changes nothing.\n\nThis changes the app's live data model, so it takes effect immediately. It doesn't change the file that defines that model in the app's source code. Since Base44 rebuilds the live model whenever the file is written or the app's code is pulled from GitHub, a change made using this endpoint may be reverted.\n\nTo change the model for good, change the entities configuration files.\n\nPass `User` as the `entity_name` to drop the custom fields from the built-in user entity. Unlike every other entity, this isn't blocked by existing records and it doesn't delete any app users. Their custom values stay in storage but stop being part of the user schema.","operationId":"delete_schema_api_apps__app_id__entity_schemas__entity_name__delete","parameters":[{"name":"entity_name","in":"path","required":true,"schema":{"type":"string","description":"Name of the entity to remove, as returned by [List entity schemas](/api-reference/list-entity-schemas). Pass `User` to drop the built-in user entity's custom fields.","title":"Entity Name"},"description":"Name of the entity to remove, as returned by [List entity schemas](/api-reference/list-entity-schemas). Pass `User` to drop the built-in user entity's custom fields.","example":"Invoice"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose entity schemas you want to work with.","title":"App Id"},"description":"ID of the app whose entity schemas you want to work with.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteEntitySchemaResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or your workspace API key lacks the `apps:deploy` scope."},"404":{"description":"App not found, or the app has no entity with this name (a workspace API key gets a 200 instead)."},"409":{"description":"The request is scoped to a feature branch. Entity schemas can only be changed on the main branch."},"428":{"description":"The entity still has records. Delete them first."}}}},"/api/apps/{app_id}/app-checkpoints":{"get":{"summary":"List checkpoints","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the specified app's checkpoints, newest first. A [checkpoint](/developers/references/app-management/get-started/concepts#checkpoints) is a saved version of the app, captured as you build.\n\nTo deploy a specific checkpoint, pass its `id` as the `checkpoint_id` to [Deploy an app](/api-reference/deploy-an-app).\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"list_checkpoints_api_apps__app_id__app_checkpoints_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app.","title":"App Id"},"description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Maximum number of checkpoints to return, between 1 and 10000. Omit to return all of them.","title":"Limit"},"description":"Maximum number of checkpoints to return, between 1 and 10000. Omit to return all of them.","example":50},{"name":"skip","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Number of checkpoints to skip before the page starts, 0 or greater. Combine with `limit` to page through results.","title":"Skip"},"description":"Number of checkpoints to skip before the page starts, 0 or greater. Combine with `limit` to page through results.","example":0}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CheckpointSummary"},"title":"Checkpoints"}}}},"400":{"description":"The `limit` is outside 1 to 10000, or `skip` is negative."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"App not found."},"422":{"description":"The `limit` or `skip` is not an integer."}}},"post":{"summary":"Create checkpoint","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSaves the app's current state as a new [checkpoint](/developers/references/app-management/get-started/concepts#checkpoints), so you can return to it later with [Restore checkpoint](/api-reference/restore-checkpoint).\n\nA checkpoint records the app's committed code, its entity schemas and its backend functions. Nothing is saved when the app has not changed since its most recent checkpoint: you get a 200 with `created` set to `false` and `reason` set to `no_changes`, and the checkpoint you already have stands.\n\n<Warning>That check is not a guarantee against duplicates. Two requests in flight at the same time can both find nothing changed and both save a checkpoint, so retrying before the first request has finished can leave you with two. Send one at a time, and read [List checkpoints](/api-reference/list-checkpoints) rather than retrying blind.</Warning>\n\nSaving a checkpoint starts its preview build in the background. Poll [List checkpoints](/api-reference/list-checkpoints) and watch the new checkpoint's `preview_status` to see when the preview is ready.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"create_checkpoint_api_api_apps__app_id__app_checkpoints_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app.","title":"App Id"},"description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateManualCheckpointRequest","default":{"name":"Manual Edits"}}}}},"responses":{"200":{"description":"Whether a checkpoint was saved, and its ID if one was.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManualCheckpointResponse"}}}},"400":{"description":"The app has no committed code yet, so there is nothing to save."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"App not found."},"409":{"description":"The app's main line is protected, so its state can't be changed directly."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/app-checkpoints/{checkpoint_id}/load":{"post":{"summary":"Restore checkpoint","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the app to a saved [checkpoint](/developers/references/app-management/get-started/concepts#checkpoints) and answers with the restored app.\n\nRestoring rolls the app's code back to the checkpoint's commit, redeploys its backend functions from that code, restores the entity schemas the checkpoint saved, and rewinds the app's chat history to the point the checkpoint was taken.\n\n<Note>A 200 does not confirm that every backend function redeployed. A function whose deployment fails is recorded and skipped rather than failing the restore, so an app that depends on its functions is worth checking afterwards.</Note>\n\n<Warning>The chat messages after the checkpoint are dropped from the conversation and do not come back. The code does come back: a restore deletes no checkpoints, so every version stays in [List checkpoints](/api-reference/list-checkpoints) and restoring a later one returns the code you rolled back from.</Warning>\n\nThe work happens while you wait, and there is a lot of it: a code rollback, a redeploy per backend function, and a schema sync. Allow for that in your client's timeout. The app's `status` is `processing` for the duration and `ready` once the restore finishes, so if your request times out, poll [Get app](/api-reference/get-app) instead of sending this again.\n\nRestoring the checkpoint the app is already on is not a no-op. There is no shortcut for that case: the rollback, the redeploy and the sync all run again.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"load_checkpoint_api_api_apps__app_id__app_checkpoints__checkpoint_id__load_post","parameters":[{"name":"checkpoint_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the checkpoint, as returned in `id` by [List checkpoints](/api-reference/list-checkpoints).","title":"Checkpoint Id"},"description":"ID of the checkpoint, as returned in `id` by [List checkpoints](/api-reference/list-checkpoints).","example":"6886b8d390dc7e2f4a2c91b3"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app.","title":"App Id"},"description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The app, as restored to the checkpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSummary"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"App not found, or the app has no checkpoint with this ID."},"409":{"description":"The app's main line is protected, or the checkpoint belongs to a different line of the app's history than the one you're restoring into."}}}},"/api/apps/{app_id}/secrets":{"post":{"summary":"Set secret","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nStores one or more secrets on the app and makes them available to its backend functions as environment variables. Send several at once by putting several keys in the body.\n\nA name that already exists is overwritten, so this both creates and updates. Names must be uppercase letters, digits, and underscores, and must start with a letter, for example `STRIPE_SECRET_KEY`. Some names are reserved by Base44 and are rejected.\n\nSetting a secret redeploys the app's backend functions so they pick up the new value.\n\n<Warning>Secret values are write-only. Nothing in this API returns them once stored, so keep your own copy of anything you can't regenerate.</Warning>","operationId":"create_or_update_secrets_api_api_apps__app_id__secrets_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to set the secret on.","title":"App Id"},"description":"ID of the app to set the secret on.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"title":"SetSecrets","description":"Each key is a secret name and each value is the secret itself."},"example":{"STRIPE_SECRET_KEY":"sk_live_51H...","SENDGRID_API_KEY":"SG.x9..."}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSecretsResponse"}}}},"400":{"description":"A secret name is not a valid environment variable name, or is reserved by Base44."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App not found."},"422":{"description":"The request body is missing, or is not a JSON object."}}}},"/api/apps/{app_id}/chat/message":{"post":{"summary":"Send chat message","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSends a message to the app's AI chat and runs the [turn](/developers/references/app-management/get-started/concepts#turns) it starts. Each turn consumes credits. This is how you build an app after creating it.\n\nSet an `X-Request-ID` header before you send. A network [retry](/developers/references/app-management/get-started/retries) carrying the same value is ignored rather than run a second time and charged again.\n\nThe request stays open until the AI finishes the whole turn, so it can take several minutes on a large change. The response is the app once the turn settles, with the messages the turn produced under `conversation.messages`. That list is ordered oldest first, so the AI's reply is the last entry whose `role` is `assistant`.\n\nA `200` response does not mean the turn succeeded, and it does not always describe a turn. Three cases come back as a `200`.\n\n- **The turn ran and finished.** `status.state` is `ready` and the app is idle again.\n- **The turn ran and failed.** `status.state` is `error`, and `status.error_source` says where it failed. A value of `paywall` means the app's workspace has no credits left and no work was done.\n- **The message was queued.** The AI was already busy with an earlier message, so the response is `{\"queued\": true}` with the queue's state and none of the fields below. Check for `queued` before you read anything else.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"add_message_api_api_apps__app_id__chat_message_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose AI chat this request acts on.","title":"App Id"},"description":"ID of the app whose AI chat this request acts on.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","title":"SendChatMessage","required":["content"],"properties":{"content":{"type":"string","description":"What you want the AI to do. Up to 100,000 characters.","example":"Add a contact form to the home page"},"file_urls":{"type":"array","items":{"type":"string"},"description":"Publicly reachable URLs of files to attach to the message, such as a screenshot or a mockup for the AI to work from. Base44 downloads each file, so a URL has to resolve without credentials.","example":["https://example.com/mockup.png"]}}},"example":{"content":"Add a contact form to the home page"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatTurnResponse"}}}},"400":{"description":"The `content` field is longer than 100,000 characters, or a URL in `file_urls` was rejected."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App not found."},"409":{"description":"The app is busy with another operation that blocks new messages."},"422":{"description":"The request body is missing `content`, or one of its fields has the wrong type."},"503":{"description":"The server is shutting down or the app's queue is unavailable. Retry the request."}}}},"/api/apps/{app_id}/chat/submit-tool-call-input":{"post":{"summary":"Submit tool-call input","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nApproves or rejects one tool call the AI is waiting on, then resumes the turn.\n\nSome steps pause for you before they run. The turn stops with an `assistant` message whose tool call has no result yet, and the AI waits. Answer it here to let the turn continue. Use [Read conversation messages](/api-reference/read-conversation-messages) to find the tool call that is waiting and take its ID.\n\nThe request stays open until the resumed turn settles, so it can take several minutes. Resuming a turn consumes credits. To answer several waiting tool calls at once, use [Submit tool-call input (batch)](/api-reference/submit-tool-call-input-batch).\n\nSet an `X-Request-ID` header before you send. A network [retry](/developers/references/app-management/get-started/retries) carrying the same value is ignored rather than resumed a second time and charged again.\n\nA `200` response does not mean the turn succeeded. Two cases come back as a `200`.\n\n- **The turn ran and finished.** `status.state` is `ready` and the app is idle again.\n- **The turn ran and failed.** `status.state` is `error`, and `status.error_source` says where it failed. A value of `paywall` means the app's workspace has no credits left.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"add_message_with_user_input_api_api_apps__app_id__chat_submit_tool_call_input_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose AI chat this request acts on.","title":"App Id"},"description":"ID of the app whose AI chat this request acts on.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","title":"SubmitToolCallInput","required":["tool_call_id","action"],"properties":{"tool_call_id":{"type":"string","description":"ID of the tool call to answer. Read it off the waiting `assistant` message returned by [Read conversation messages](/api-reference/read-conversation-messages).","example":"toolu_01A9FJd3kP2mNqRs7VwXyZ4b"},"action":{"type":"string","enum":["approved","rejected"],"description":"Whether to let the tool call run or turn it down. Rejecting tells the AI to continue without doing that step.","example":"approved"}}},"example":{"tool_call_id":"toolu_01A9FJd3kP2mNqRs7VwXyZ4b","action":"approved"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatTurnResponse"}}}},"400":{"description":"The app has unsaved sandbox changes that could not be committed first. Retry the request."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App not found, or no such tool call is waiting."},"409":{"description":"The app is busy with another operation that blocks this resume."},"422":{"description":"The request body is missing `tool_call_id` or `action`, or `action` is not `approved` or `rejected`."},"503":{"description":"The server is shutting down. Retry the request."}}}},"/api/apps/{app_id}/chat/submit-tool-call-input/batch":{"post":{"summary":"Submit tool-call input (batch)","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nAnswers several waiting tool calls at once with the same `action`, then resumes the turn. Use it instead of calling [Submit tool-call input](/api-reference/submit-tool-call-input) once per tool call.\n\nEach tool call gets its own entry in `results`, so one of them can fail while the rest succeed. Check every entry rather than assuming the whole batch worked. The app is returned under `app`, not at the top level as it is on the single-tool-call endpoint.\n\nThe request stays open until the resumed turn settles, so it can take several minutes. Resuming a turn consumes credits.\n\nSet an `X-Request-ID` header before you send. A network [retry](/developers/references/app-management/get-started/retries) carrying the same value is ignored rather than resumed a second time and charged again. It comes back with an empty `results` array, which means nothing ran again rather than that the tool calls were rejected.\n\nA `200` response does not mean the turn succeeded. Two cases come back as a `200`.\n\n- **The turn ran and finished.** `app.status.state` is `ready` and the app is idle again.\n- **The turn ran and failed.** `app.status.state` is `error`, and `app.status.error_source` says where it failed. A value of `paywall` means the app's workspace has no credits left.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"batch_submit_tool_call_input_api_api_apps__app_id__chat_submit_tool_call_input_batch_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose AI chat this request acts on.","title":"App Id"},"description":"ID of the app whose AI chat this request acts on.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","title":"SubmitToolCallInputBatch","required":["tool_call_ids","action"],"properties":{"tool_call_ids":{"type":"array","items":{"type":"string"},"description":"IDs of the tool calls to answer, up to 100. They all get the same `action`, and a repeated ID is answered once.","example":["toolu_01A9FJd3kP2mNqRs7VwXyZ4b","toolu_01B8GKe4lQ3nOrSt8WxYzA5c"]},"action":{"type":"string","enum":["approved","rejected"],"description":"Whether to let these tool calls run or turn them down.","example":"approved"}}},"example":{"tool_call_ids":["toolu_01A9FJd3kP2mNqRs7VwXyZ4b"],"action":"approved"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchToolCallResponse"}}}},"400":{"description":"The app has unsaved sandbox changes that could not be committed first. Retry the request."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App not found."},"409":{"description":"The app is busy with another operation that blocks this resume."},"422":{"description":"The request body is missing `tool_call_ids` or `action`, `action` is not `approved` or `rejected`, or more than 100 IDs were sent."},"503":{"description":"The server is shutting down. Retry the request."}}}},"/api/apps/{app_id}/chat/stop":{"post":{"summary":"Stop generation","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nStops the turn the AI is currently running and returns the app once it has settled.\n\nChanges the AI had already saved as a [checkpoint](/developers/references/app-management/get-started/concepts#checkpoints) are kept. Work still in progress may be discarded, so do not count on a half-finished change surviving. The conversation keeps the partial turn, so send a new message with [Send chat message](/api-reference/send-chat-message) to carry on from there.\n\nStopping an app that is not running anything is not an error. You get the app back unchanged, which makes this safe to call whenever you are unsure whether a turn is still going.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"stop_chat_api_api_apps__app_id__chat_stop_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose AI chat this request acts on.","title":"App Id"},"description":"ID of the app whose AI chat this request acts on.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatTurnResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App not found."}}}},"/api/apps/{app_id}/chat/message/{message_id}/undo":{"post":{"summary":"Undo message","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRolls the app back to the [checkpoint](/developers/references/app-management/get-started/concepts#checkpoints) a message produced, undoing everything the AI did after it.\n\nPass the ID of the message you want to roll back to. Find it with [Read conversation messages](/api-reference/read-conversation-messages). Only a message that carries a `checkpoint_id` can be undone. The app's code goes back to that saved version.\n\nThis changes the app you are building, not the app your users see. Publish with [Deploy an app](/api-reference/deploy-an-app) to make the rollback live.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"undo_message_api_api_apps__app_id__chat_message__message_id__undo_post","parameters":[{"name":"message_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the message to roll back to. Take it from [Read conversation messages](/api-reference/read-conversation-messages).","title":"Message Id"},"description":"ID of the message to roll back to. Take it from [Read conversation messages](/api-reference/read-conversation-messages).","example":"7f3a1c88-52d4-4a0e-9b31-2c6f0d8e4a19"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose AI chat this request acts on.","title":"App Id"},"description":"ID of the app whose AI chat this request acts on.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatTurnResponse"}}}},"400":{"description":"The message has no checkpoint to roll back to."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App, message, or checkpoint not found."},"409":{"description":"The app is busy with another operation that blocks the rollback, or the request is scoped to a feature branch. A rollback restores a checkpoint, which is app-wide, so it can only run on the main branch."}}}},"/api/apps/{app_id}/chat/edit-and-resend":{"post":{"summary":"Edit and resend","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReplaces an earlier user message and rebuilds from there. Base44 rolls the app back to just before that message, then runs your new one in its place.\n\nUse it to reword a request that took the AI in the wrong direction, instead of piling a correction on top. Everything the AI did from `message_id` onwards is undone first, so those changes are gone. To keep them, send a new message with [Send chat message](/api-reference/send-chat-message) instead.\n\nThe request stays open until the new turn settles, so it can take several minutes. It consumes credits like any other turn.\n\nSet an `X-Request-ID` header before you send. A network [retry](/developers/references/app-management/get-started/retries) carrying the same value is ignored rather than run a second time and charged again.\n\nA `200` response does not mean the turn succeeded. Two cases come back as a `200`.\n\n- **The turn ran and finished.** `status.state` is `ready` and the app is idle again.\n- **The turn ran and failed.** `status.state` is `error`, and `status.error_source` says where it failed. A value of `paywall` means the app's workspace has no credits left.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"edit_and_resend_api_api_apps__app_id__chat_edit_and_resend_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose AI chat this request acts on.","title":"App Id"},"description":"ID of the app whose AI chat this request acts on.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","title":"EditAndResend","required":["message_id","content"],"properties":{"message_id":{"type":"string","description":"ID of the message you are replacing. Everything from this message onwards is undone before the new one is sent.","example":"7f3a1c88-52d4-4a0e-9b31-2c6f0d8e4a19"},"content":{"type":"string","description":"What to send instead. Up to 100,000 characters.","example":"Add a contact form to the home page, with a phone field"},"file_urls":{"type":"array","items":{"type":"string"},"description":"Publicly reachable URLs of files to attach to the new message. Base44 downloads each file, so a URL has to resolve without credentials.","example":["https://example.com/mockup.png"]}}},"example":{"message_id":"7f3a1c88-52d4-4a0e-9b31-2c6f0d8e4a19","content":"Add a contact form to the home page, with a phone field"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatTurnResponse"}}}},"400":{"description":"The `content` field is longer than 100,000 characters, or the message has no checkpoint to roll back to."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App, message, or checkpoint not found."},"409":{"description":"The app is busy with another operation that blocks the rollback, or the request is scoped to a feature branch. A rollback restores a checkpoint, which is app-wide, so it can only run on the main branch."},"422":{"description":"The request body is missing `message_id` or `content`, or one of its fields has the wrong type."},"503":{"description":"The server is shutting down, or the app's queue could not be paused for the edit. Retry the request."}}}},"/api/apps/{app_id}/chat/full-conversation":{"get":{"summary":"Read conversation messages","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the app's AI chat, both the messages sent to the AI and the replies it produced, oldest first.\n\nAn assistant message can have empty `content` when the whole turn is carried by tool calls, so treat an empty message as work the AI did rather than an error. Messages with `hidden` set to `true` are internal and do not appear in the app editor, so skip them to reconstruct the transcript shown there.\n\nA tool call whose `status` is `waiting_for_user_input` means the AI has paused and cannot continue until that call is answered. Read its `arguments_string` to see what it is asking to do, then answer it with [Submit tool-call input](/api-reference/submit-tool-call-input) to let the turn continue. A waiting call carries its arguments in full, while a call that is not waiting can carry them cut short. The browser-typing tools are redacted either way, so a call from one of those cannot be reviewed before approving it.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"get_full_conversation_api_api_apps__app_id__chat_full_conversation_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose conversation to read.","title":"App Id"},"description":"ID of the app whose conversation to read.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Maximum number of messages to return, counted back from the newest. Omit to return the whole conversation.","title":"Limit"},"description":"Maximum number of messages to return, counted back from the newest. Omit to return the whole conversation.","example":20},{"name":"skip","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Number of messages to skip, counted back from the newest. Combine with `limit` to page back through the conversation. For example, `skip=20` with `limit=20` returns the 20 messages before the 20 most recent.","default":0,"title":"Skip"},"description":"Number of messages to skip, counted back from the newest. Combine with `limit` to page back through the conversation. For example, `skip=20` with `limit=20` returns the 20 messages before the 20 most recent.","example":0}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationDetail"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App not found."},"422":{"description":"The `limit` or `skip` is not an integer."}}}},"/api/apps/{app_id}/security/scan":{"get":{"summary":"Get security scan","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the app's latest security scan: what it found, and whether it still reflects the app.\n\nRead `status` before `result`. The two are independent, so a `result` can be present on a status that says it is stale, and absent on one that says a scan is in progress.\n\nThe findings are grouped by what the scan looked at. `rls_recommendations` cover entities whose row-level security is too open, `hardcoded_secrets` and `backend_functions` cover the app's own code and functions, `dependency_vulnerabilities` cover its npm packages, `static_code_findings` come from reading the code, and `header_recommendations` cover the published app's HTTP headers.\n\nTwo of those groups depend on what is switched on for the app rather than on what the scan found. `static_code_findings` comes back empty unless `static_code_enabled` is `true`, and `dependency_vulnerabilities` is empty for a caller outside that rollout. Both are empty lists rather than absent, so an empty group is not evidence that there is nothing to find, and `static_code_enabled` is what tells the two apart.\n\nThis read never starts a scan. Use [Run security scan](/api-reference/run-security-scan) for that, then poll here while `status` is `pending` or `scanning`. Both mean a scan will settle on its own, so treating only `scanning` as in progress stops the poll early on a queued scan and reads whatever findings the previous one left.\n\n<Note>Findings are only ever as fresh as the scan that produced them. On `out_of_date` the app has changed since, so treat the findings as a previous snapshot and run a new scan before acting on them.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"get_scan_status_api_apps__app_id__security_scan_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to scan.","title":"App Id"},"description":"ID of the app to scan.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The scan's state and its findings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecurityScanStatus"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App not found."}}},"post":{"summary":"Run security scan","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nScans the app for security problems and returns the findings.\n\nThe response is the same shape as [Get security scan](/api-reference/get-security-scan), and which of two things you get depends on whether Base44 already has a current answer:\n\n- If the last scan still matches the app, you get it straight back with `status` set to `up_to_date`, and the `X-Scan-Source` response header set to `cache`. Nothing is re-scanned.\n- Otherwise a scan starts in the background and you get `status` set to `scanning` with the previous findings still in `result`, and `X-Scan-Source` set to `async`. Poll [Get security scan](/api-reference/get-security-scan) while `status` is `pending` or `scanning`, since both mean a scan is still going to settle.\n\nSo a 200 here does not mean a scan ran, and it does not mean the findings in the body are current. Read `status` and the `X-Scan-Source` header to tell the two apart.\n\nA real scan reads the app's code and runs a language model over it, which takes a while and is why it runs in the background rather than on your connection. This endpoint is limited to 5 requests per minute.\n\n<Note>Findings are only ever as fresh as the scan that produced them. On `out_of_date` the app has changed since, so treat the findings as a previous snapshot and run a new scan before acting on them.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"run_scan_api_apps__app_id__security_scan_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to scan.","title":"App Id"},"description":"ID of the app to scan.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"language","in":"query","required":false,"schema":{"enum":["en","ja","de","es","fr","pt"],"type":"string","description":"Language to return generated text in, as a lowercase two-letter code. An unsupported value is rejected with a 422.","default":"en","title":"Language"},"description":"Language to return generated text in, as a lowercase two-letter code. An unsupported value is rejected with a 422.","example":"de"}],"responses":{"200":{"description":"The findings, or the state of the scan that just started.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecurityScanStatus"}}},"headers":{"X-Scan-Source":{"description":"Where the response came from. `cache` means the existing result was returned and nothing was re-scanned. `async` means a scan started in the background and `result` holds the previous findings.","schema":{"type":"string","enum":["cache","async"],"example":"cache"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded (5 requests per minute)."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/analytics/names":{"get":{"summary":"List analytics event names","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the names of the analytics events the app has tracked, most frequent first, with how many times each one was recorded.\n\nUse this to discover what an app tracks before you query it. Pass a `name` from the response as the `event_name` to [Query analytics events](/api-reference/query-analytics-events), [Aggregate analytics events over time](/api-reference/aggregate-analytics-events-over-time), or [List analytics event properties](/api-reference/list-analytics-event-properties).\n\nBase44 keeps analytics events for 60 days, so counts cover the last 60 days and an event the app stopped sending before that no longer appears. The response returns at most the 1000 most frequent names.\n\n<Note>Events are written asynchronously, so an event the app tracked in the last few seconds can be missing from the response.</Note>","operationId":"get_event_names_api_apps__app_id__analytics_names_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose analytics to read.","title":"App Id"},"description":"ID of the app whose analytics to read.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventNamesResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App not found."}}}},"/api/apps/{app_id}/analytics/{event_name}/properties":{"get":{"summary":"List analytics event properties","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the property keys the app has sent with a given event, so you know what you can filter, aggregate, and group by.\n\nProperties are whatever the app passes to `base44.analytics.track()`, so they differ per event. Get the event names first with [List analytics event names](/api-reference/list-analytics-event-names).\n\nTo use a key from the response, prefix it with `properties.`. For example, pass `properties.plan` as a filter field in [Query analytics events](/api-reference/query-analytics-events), or as a metric `field` in [Aggregate analytics events by field](/api-reference/aggregate-analytics-events-by-field).\n\n<Warning>Only a key made up of letters, digits, and underscores, starting with a letter or an underscore, can be used that way. An app can track a property whose key holds a space, a hyphen, or a leading digit, and this endpoint returns it, but you cannot filter or aggregate on it. Rename such a property in the app to query it.</Warning>\n\nKeys come from the events retained over the last 60 days. The response returns at most 100 keys and does not say whether it truncated, so an event that carries more than 100 distinct keys reports only some of them.","operationId":"get_event_property_keys_api_apps__app_id__analytics__event_name__properties_get","parameters":[{"name":"event_name","in":"path","required":true,"schema":{"type":"string","description":"Name of the event whose property keys to return, exactly as the app tracked it. An event the app never tracked returns an empty list.","title":"Event Name"},"description":"Name of the event whose property keys to return, exactly as the app tracked it. An event the app never tracked returns an empty list.","example":"checkout_completed"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose analytics to read.","title":"App Id"},"description":"ID of the app whose analytics to read.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventPropertyKeysResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App not found."}}}},"/api/apps/{app_id}/analytics/query":{"post":{"summary":"Query analytics events","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the app's individual analytics events for one event name, newest first, with the properties the app sent on each one.\n\nThe time range defaults to the last 7 days. To use a different range, put `timestamp` bounds in the `q` filter, for example `{\"timestamp\": {\"gte\": \"2026-08-01T00:00:00Z\", \"lt\": \"2026-08-08T00:00:00Z\"}}`. Base44 applies the bounds as `timestamp >= start` and `timestamp < end`, so `gt` behaves like `gte` and `lte` behaves like `lt`. Events are kept for 60 days, so an older range returns nothing.\n\nPage through the results with `limit` and `offset`. When `has_more` is `true`, send the returned `next_offset` as the next request's `offset`. `total` counts every matching event, not just the ones on this page.\n\n`q` is a JSON object serialized to a string. Each key is a field, and its value is either a literal to match for equality or an object of operators, as in `{\"user_id\": \"6891ab34d2f07e5c1b9a2d48\", \"properties.amount\": {\"gte\": 50}}`. The comparison operators are `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, and `regex`. Combine expressions with `and` or `or`, each taking an array, and negate one with `not`.\n\n`event_id`, `event_name`, `timestamp`, `user_id`, `session_id`, and `page_url` are top-level fields. Every other key reads as an event property, so `plan` and `properties.plan` mean the same thing. Get the available property keys from [List analytics event properties](/api-reference/list-analytics-event-properties). Prefix a key with `metadata.` to filter on the device information Base44 captures itself: `metadata.device_type`, `metadata.os`, and `metadata.country`.\n\n<Warning>`regex` matches a substring, not a regular expression. `{\"page_url\": {\"regex\": \"/checkout\"}}` matches any URL containing `/checkout`, and regular expression syntax such as `^` or `.*` matches literally. Add `\"options\": \"i\"` next to it for a case-insensitive match.</Warning>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"query_events_api_apps__app_id__analytics_query_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose analytics to read.","title":"App Id"},"description":"ID of the app whose analytics to read.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryEventsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsEventPage"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/analytics/timeseries":{"post":{"summary":"Aggregate analytics events over time","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nAggregates the app's analytics events into time buckets, so you can chart them. Send the metrics you want per bucket, and Base44 returns them per bucket plus a total over the whole range.\n\nThe range defaults to the last 30 days. Events are kept for 60 days, so an older range returns nothing. `timestamp` bounds inside `q` override `start_time` and `end_time`.\n\nBase44 buckets by one of a fixed set of intervals: 1, 2, 3, 4, 6, 8 and 12 hours, 1, 2 and 3 days, 1 and 2 weeks, and 30 days. One hour is the finest available. Omit `bucket_size_ms` to get the finest interval that keeps the bucket count within `max_buckets`. Pass it to choose an interval yourself. Base44 rounds it down to the nearest supported interval, and rounds it up to one hour when you ask for anything finer, so a sub-hour request comes back coarser than you asked. Read the interval it used off `bucket_interval` in the response rather than assuming your request was honored. Keep `bucket_size_ms` coarse enough that the range divides into no more than `max_buckets` buckets.\n\nBuckets with no matching events are left out, so chart your own zero for the gaps. `count` and `count_unique` work on any field, while `sum`, `avg`, `min`, `max` and the percentiles read the field as a number and report `0` for a bucket whose values are all non-numeric.","operationId":"timeseries_api_apps__app_id__analytics_timeseries_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose analytics to read.","title":"App Id"},"description":"ID of the app whose analytics to read.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeseriesRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeseriesResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/analytics/grouped":{"post":{"summary":"Aggregate analytics events by field","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nBreaks the app's analytics events down by the value of a field, so you can rank them: the busiest pages, the countries your users come from, the most tracked event names.\n\nEvery metric must set `field`, and that field is what the metric groups by. `user_id`, `session_id`, `event_id`, `event_name` and `page_url` group by those top-level fields, which is what the default metric does. A `metadata.` prefix groups by the device information Base44 captures itself: `metadata.country`, `metadata.device_type`, and `metadata.os`. Any other name groups by an event property. Groups come back largest first, capped at `max_groups` per metric. The range defaults to the last 30 days, and events are kept for 60 days.\n\nUse `count` for the metric function. Because a metric's `field` is both the field it groups by and the field its function reads, the other functions return a value the grouping already implies. A `count_unique` reports `1` for every group, and `sum`, `avg`, `min`, `max` and the percentiles report the group's own value.\n\nEach entry in `groups` carries exactly one metric. When you send several metrics, the list holds one entry per group value per metric, so read the metric name off each entry's `metrics` key instead of assuming a single set of groups. `group_count` counts those entries across all metrics, not the number of distinct group values.\n\n<Note>This endpoint takes no filter expression. To break down a filtered set of events, filter by event name here, or use [Aggregate analytics events over time](/api-reference/aggregate-analytics-events-over-time) with `q` and a single bucket.</Note>","operationId":"grouped_api_apps__app_id__analytics_grouped_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose analytics to read.","title":"App Id"},"description":"ID of the app whose analytics to read.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupedAggregationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupedAggregationResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/analytics/stats/users":{"get":{"summary":"Get analytics user stats","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns how many of the app's users are active, live, and inactive, for an at-a-glance dashboard.\n\nA user counts once, in the bucket its most recent event falls into: `active_users` for the last 7 days, `inactive_7d` for 7 to 30 days ago, and `inactive_30d` for longer ago than that. Base44 keeps analytics events for 60 days, so `inactive_30d` covers 30 to 60 days and a user who has been away longer drops out of the counts entirely.\n\n`live_users` is counted separately, from the app's heartbeats rather than its events, and covers the last 2 minutes.\n\nOnly users the app attributes its events to are counted. Events the app tracks without a user are not.\n\n<Note>Each counter reports `0` when Base44 cannot read it, with a `200`, so treat a sudden drop to zero as a possible read failure rather than a real one.</Note>","operationId":"get_user_stats_api_apps__app_id__analytics_stats_users_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose analytics to read.","title":"App Id"},"description":"ID of the app whose analytics to read.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserStatsResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App not found."}}}},"/api/apps/{app_id}/coding/export-to-zip":{"get":{"summary":"Export app source code","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nDownloads the app's source code as a zip archive. The archive reflects the app's current saved code.","operationId":"export_to_zip_api_apps__app_id__coding_export_to_zip_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose source code to export.","title":"App Id"},"description":"ID of the app whose source code to export.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/zip":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App not found."}}}},"/api/apps/{app_id}/sandbox/preview-url":{"get":{"summary":"Get preview URL","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns a URL that serves the app as it currently stands in the app editor, including changes that haven't been published yet. Use it to look at your work in progress. Use [Deploy an app](/api-reference/deploy-an-app) to put it in front of your users.\n\nThe preview is served by a sandbox that shuts down when it goes unused. If none is running, this starts one, so the first call after a quiet period takes noticeably longer than later ones. The `sandbox_info.cold_start` field tells you which happened.\n\nA `409` usually means the app's code doesn't currently build. The response body says what failed, so send that to the AI with [Send chat message](/api-reference/send-chat-message) and ask for the preview again.","operationId":"get_preview_url_api_apps__app_id__sandbox_preview_url_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","title":"App Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewUrlResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app."},"404":{"description":"App not found."},"409":{"description":"The app's development server couldn't start, usually because its code doesn't build."},"500":{"description":"The preview couldn't be produced."}}}},"/api/apps/{app_id}/deploy":{"post":{"summary":"Deploy an app","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nDeploys your app to production, publishing your latest changes so they go live for its users. By default the app's current version is deployed. To deploy a specific saved version instead, pass its ID in the request body.","operationId":"deploy_api_apps__app_id__deploy_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to deploy.","title":"App Id"},"description":"ID of the app to deploy.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/DeployRequest"},{"type":"null"}],"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeployResponse"}}}},"400":{"description":"The app cannot be published, for example a native mobile app."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"App is blocked, or the caller can't publish it."},"404":{"description":"App not found."},"409":{"description":"App is processing, or the target is a branch checkpoint."},"422":{"description":"`checkpoint_id` is not a string."}}}},"/api/apps/{app_id}/virality/start":{"post":{"summary":"Start social content flow","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nStarts the social content flow for an app. Base44 reads the app, returns a one-sentence analysis of it, and asks 2 or 3 questions whose answers shape the content strategy.\n\nAnswer them with [Submit answers](/api-reference/submit-answers). Calling this endpoint again restarts the flow. It discards any answers, strategy, and content plan already stored for the app. It fails with a 409 while a content plan is generating, so reset or finish that first.\n\nThis endpoint calls a language model, so expect it to take a few seconds. It shares a limit of 15 requests per minute with the other social content endpoints, except [Get social content state](/api-reference/get-social-content-state) and [Generate a post image](/api-reference/generate-a-post-image), which have their own.\n\n<Note>The questions are generated per app, so both their number and their wording vary between calls. Read `id` and `type` off each question rather than assuming a fixed set.</Note>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"start_api_api_apps__app_id__virality_start_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app.","title":"App Id"},"description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartFlowResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or you used a workspace API key."},"404":{"description":"App not found, or the social content feature is not enabled for your account."},"409":{"description":"A content plan is currently generating for this app."},"429":{"description":"Rate limit exceeded (15 requests per minute)."},"500":{"description":"The app analysis failed. Retry the request."}}}},"/api/apps/{app_id}/virality/submit-answers":{"post":{"summary":"Submit answers","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSubmits your answers to the questions from [Start social content flow](/api-reference/start-social-content-flow) and returns a content strategy to review.\n\nKey each answer by the question `id`. Send `social_url` to match the writing voice of your own social profile. Answers replace any previously submitted for the app, and generating a new strategy discards the previous one. This endpoint fails with a 409 while a content plan is generating.\n\nAccept the strategy by calling [Generate content plan](/api-reference/generate-content-plan), or change it first with [Refine content strategy](/api-reference/refine-content-strategy).\n\nThis endpoint calls a language model, so expect it to take a few seconds. It shares a limit of 15 requests per minute with the other social content endpoints.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"submit_answers_api_api_apps__app_id__virality_submit_answers_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app.","title":"App Id"},"description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitAnswersPayload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrategyResponse"}}}},"400":{"description":"The request is invalid."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or you used a workspace API key."},"404":{"description":"App not found, or the social content feature is not enabled for your account."},"409":{"description":"A content plan is currently generating for this app."},"429":{"description":"Rate limit exceeded (15 requests per minute)."},"500":{"description":"Generating the strategy failed. Retry the request."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/virality/refine-strategy":{"post":{"summary":"Refine content strategy","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRewrites the current content strategy according to your feedback and returns the new version. Call it as many times as you need before generating a content plan.\n\nSubmit answers first. This endpoint fails with a 409 unless the app already has a strategy, and while a content plan is generating.\n\n<Warning>Refining the strategy discards the app's existing content plan, including every post and every generated image. Refine before you call [Generate content plan](/api-reference/generate-content-plan), not after.</Warning>\n\nThis endpoint calls a language model, so expect it to take a few seconds. It shares a limit of 15 requests per minute with the other social content endpoints.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"refine_strategy_api_api_apps__app_id__virality_refine_strategy_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app.","title":"App Id"},"description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefineStrategyPayload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrategyResponse"}}}},"400":{"description":"The `feedback` field is empty."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or you used a workspace API key."},"404":{"description":"App not found, the app has no social content state yet, or the social content feature is not enabled for your account."},"409":{"description":"The app has no strategy to refine yet, or a content plan is currently generating."},"429":{"description":"Rate limit exceeded (15 requests per minute)."},"500":{"description":"Refining the strategy failed. Retry the request."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/virality/generate":{"post":{"summary":"Generate content plan","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nGenerates the full content plan: a set of posts for each platform you approve, written from the app's accepted strategy.\n\nSubmit answers and accept a strategy first. This endpoint fails with a 409 unless the app has a strategy, and while another generation is already running for the app.\n\nThis request costs 10 credits and fails with a 402 when the workspace is out of quota. It runs the whole generation inline, one language model call per platform plus the first platform's images, so it can take **several minutes** with 3 platforms. Use a long client timeout. The remaining images generate in the background, so poll [Get social content state](/api-reference/get-social-content-state) to pick up the `image_url` values that land after the response.\n\nGenerating a plan replaces any plan the app already has, including its generated images.\n\nThis endpoint shares a limit of 15 requests per minute with the other social content endpoints.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"generate_api_api_apps__app_id__virality_generate_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app.","title":"App Id"},"description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeneratePlanPayload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentPlanResponse"}}}},"400":{"description":"None of the requested platforms are supported."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The workspace is out of credits."},"403":{"description":"You don't have editor access to this app, or you used a workspace API key."},"404":{"description":"App not found, the app has no social content state yet, or the social content feature is not enabled for your account."},"409":{"description":"The app has no accepted strategy yet, or a content plan is already generating."},"429":{"description":"Rate limit exceeded (15 requests per minute)."},"500":{"description":"Generating the content plan failed. Retry the request."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/virality/posts/{post_id}/refine":{"post":{"summary":"Refine a post","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRewrites a single post in the content plan according to your request, and returns the whole updated plan.\n\nTake `post_id` from a post in the plan. Only that post changes. Every other post is returned unchanged. To replace a post's text yourself instead of having it rewritten, use [Update post content](/api-reference/update-post-content).\n\nThis request costs 1 credit and fails with a 402 when the workspace is out of quota. It calls a language model, so expect it to take a few seconds.\n\n<Warning>Editing the same post while a refinement is in flight discards the refinement, and the request fails with a **500**, not a 409. Retrying is the right response, but check the post's current `content` first, because your edit is the version that survived. A 409 here means something different. It signals repeated write conflicts on the plan that did not settle.</Warning>\n\nThis endpoint shares a limit of 15 requests per minute with the other social content endpoints.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"refine_post_api_api_apps__app_id__virality_posts__post_id__refine_post","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the post, as returned in the content plan. Must be a UUID.","title":"Post Id"},"description":"ID of the post, as returned in the content plan. Must be a UUID.","example":"3f2504e0-4f89-11d3-9a0c-0305e82c3301"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app.","title":"App Id"},"description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefinePostPayload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentPlanResponse"}}}},"400":{"description":"The `post_id` is not a UUID, or `request` is empty."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The workspace is out of credits."},"403":{"description":"You don't have editor access to this app, or you used a workspace API key."},"404":{"description":"App not found, the app has no content plan, the post isn't in it, or the social content feature is not enabled for your account."},"409":{"description":"Repeated write conflicts on this plan did not settle, or a content plan is currently generating. Retry the request."},"429":{"description":"Rate limit exceeded (15 requests per minute)."},"500":{"description":"The post was edited while refining and the refinement was discarded, or the model returned no usable change. Retry the request."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/virality/state":{"get":{"summary":"Get social content state","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns everything stored for the app's social content flow: how far it has got, the questions and your answers, the current strategy, and the content plan.\n\nRead `stage` to see where the app is. An app that never started the flow returns `stage` of `idle` and `null` for everything else, rather than a 404.\n\nPoll this endpoint after [Generate content plan](/api-reference/generate-content-plan) returns. Only the first platform's images are generated inline, so the rest of the `image_url` values appear here as they finish. A `stage` of `generating` means a plan is still being built, and the endpoints that change the plan fail with a 409 until it finishes.\n\nThis endpoint is limited to 40 requests per minute, separately from the other social content endpoints.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"get_virality_state_api_apps__app_id__virality_state_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app.","title":"App Id"},"description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SocialContentStateResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or you used a workspace API key."},"404":{"description":"App not found, or the social content feature is not enabled for your account."},"429":{"description":"Rate limit exceeded (40 requests per minute)."},"500":{"description":"Loading the state failed. Retry the request."}}}},"/api/apps/{app_id}/virality/posts/{post_id}":{"put":{"summary":"Update post content","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReplaces a post's text with the text you supply, and returns the whole updated plan.\n\nThis is the plain-edit counterpart to [Refine a post](/api-reference/refine-a-post). It saves the text you send, calls no language model, and costs no credits. Only the post's `content` changes, so its image and every other post stay as they are.\n\n<Note>The text is sanitized before it is stored, so what you read back can differ from what you sent. XML and HTML tags, code fences, and lines starting `system:`, `assistant:`, or `user:` are removed. Read `content` off the response rather than assuming your input was stored verbatim.</Note>\n\nA 409 means repeated write conflicts on the plan did not settle, so retry the request. This endpoint shares a limit of 15 requests per minute with the other social content endpoints.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"update_post_api_api_apps__app_id__virality_posts__post_id__put","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the post, as returned in the content plan. Must be a UUID.","title":"Post Id"},"description":"ID of the post, as returned in the content plan. Must be a UUID.","example":"3f2504e0-4f89-11d3-9a0c-0305e82c3301"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app.","title":"App Id"},"description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePostPayload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentPlanResponse"}}}},"400":{"description":"The `post_id` is not a UUID."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or you used a workspace API key."},"404":{"description":"App not found, the app has no content plan, the post isn't in it, or the social content feature is not enabled for your account."},"409":{"description":"Repeated write conflicts on this plan did not settle, or a content plan is currently generating. Retry the request."},"429":{"description":"Rate limit exceeded (15 requests per minute)."},"500":{"description":"Saving the post failed. Retry the request."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/virality/posts/{post_id}/generate-image":{"post":{"summary":"Generate a post image","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nGenerates the image for a single post and saves it on the plan.\n\nThe post's own `image_prompt` wins over the `image_prompt` you send, so the request body only decides the prompt for a post that has none. To change an image that already exists, send `refinement_instruction`.\n\n<Note>If the post already has an `image_url` and you send no `refinement_instruction`, the existing image is returned as-is. Nothing is generated and no credits are charged.</Note>\n\nGenerating an image costs 1 credit and fails with a 402 when the workspace is out of quota. Image generation is budgeted at up to 60 seconds per attempt, so use a client timeout above that.\n\nThis endpoint is limited to 12 requests per minute, separately from the other social content endpoints.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"generate_image_api_api_apps__app_id__virality_posts__post_id__generate_image_post","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the post, as returned in the content plan. Must be a UUID.","title":"Post Id"},"description":"ID of the post, as returned in the content plan. Must be a UUID.","example":"3f2504e0-4f89-11d3-9a0c-0305e82c3301"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app.","title":"App Id"},"description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateImagePayload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostImageResponse"}}}},"400":{"description":"The `post_id` is not a UUID."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The workspace is out of credits."},"403":{"description":"You don't have editor access to this app, or you used a workspace API key."},"404":{"description":"App not found, the app has no content plan, the post isn't in it, or the social content feature is not enabled for your account."},"409":{"description":"A content plan is currently generating for this app."},"429":{"description":"Rate limit exceeded (12 requests per minute)."},"500":{"description":"Generating or saving the image failed. Retry the request."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/virality/reset":{"post":{"summary":"Reset social content flow","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nClears the app's social content flow so you can start over from [Start social content flow](/api-reference/start-social-content-flow).\n\n<Warning>This discards the questions, your answers, the strategy, and the content plan with every post and generated image. It also drops any scheduled post proposals. None of it is recoverable, and the credits already spent are not refunded.</Warning>\n\nThe standalone teaser posts are deliberately kept, so a reset does not regenerate them.\n\nThe response is the same whether or not the app had anything to clear, so a 200 is not evidence that a plan existed. This endpoint shares a limit of 15 requests per minute with the other social content endpoints.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"reset_plan_api_api_apps__app_id__virality_reset_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app.","title":"App Id"},"description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or you used a workspace API key."},"404":{"description":"App not found, or the social content feature is not enabled for your account."},"429":{"description":"Rate limit exceeded (15 requests per minute)."},"500":{"description":"The reset failed. Retry the request."}}}},"/api/apps/{app_id}/custom-email-domains":{"post":{"summary":"Enable email sending for a domain","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nTurns on email sending from a domain the app already owns, so your app's email comes from your own address instead of Base44's.\n\nThe domain has to be connected to the app first, and ready. A domain you brought yourself must be verified, and one bought through Base44 must have finished propagating. Until then the call fails, and the message says which requirement is missing.\n\nAn app sends emails from one domain at a time. If one is already set up, this call is rejected whatever state that domain is in. Disable it first, or use [Replace the email domain](/api-reference/replace-the-email-domain), which keeps the current domain sending while the new one verifies.\n\nEmail isn't live when this returns. The domain still needs DNS records in place, and who publishes them depends on who runs its DNS:\n\n- When Base44 runs the DNS, it writes the records itself and the domain moves toward verification on its own.\n- When you run the DNS, publish the records yourself. The domain waits at `pending_user_dns_configuration` until they resolve.\n\nEither way the records come back in `dns_records`, so you can pass them to whoever manages the domain's DNS.\n\nPoll [List email domains](/api-reference/list-email-domains) until `configuration_status` reads `active`, which is when mail starts sending.\n\nTurning a domain back on after you disabled it skips the DNS and verification steps. It returns to the state it was in, so a domain that was already sending resumes at once.","operationId":"create_email_domain_api_apps__app_id__custom_email_domains_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose email domains you want to work with.","title":"App Id"},"description":"ID of the app whose email domains you want to work with.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEmailDomainRequest"}}}},"responses":{"200":{"description":"Setup started. Read `status` and `dns_records`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEmailDomainResponse"}}}},"400":{"description":"This domain can't send mail."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"This workspace's plan doesn't include custom domains."},"403":{"description":"You don't have access to this app, you used a workspace API key, or the email domain is suspended. A suspended domain says why in the message."},"404":{"description":"This domain isn't connected to this app, or the app doesn't exist."},"409":{"description":"This app already has an email domain set up, whether or not it's sending yet. Disable it first, or use [Replace the email domain](/api-reference/replace-the-email-domain)."},"412":{"description":"The domain isn't ready yet. It still needs verifying, or its DNS hasn't finished propagating."},"422":{"description":"The request body is missing a required field or has an invalid value."},"429":{"description":"Rate limit exceeded. The base limit is 10 requests per minute. See [Rate limits](/developers/references/app-management/get-started/rate-limits) for the multiplier your plan gets."}}},"patch":{"summary":"Update email sender details","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nChanges the name or address your app's mail is sent from.\n\nThe domain part of `from_email` has to be a domain this app already has set up for email. The call edits that domain's sender details and nothing else, so it never changes which domain your mail goes out from. Use [Replace the email domain](/api-reference/replace-the-email-domain) for that.\n\nThe change applies at once and doesn't re-run verification, so a domain that was `active` keeps sending.","operationId":"update_email_domain_api_apps__app_id__custom_email_domains_patch","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose email domains you want to work with.","title":"App Id"},"description":"ID of the app whose email domains you want to work with.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEmailDomainRequest"}}}},"responses":{"200":{"description":"The updated sender details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEmailDomainResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, you used a workspace API key, or the email domain is suspended. A suspended domain says why in the message."},"404":{"description":"The domain in `from_email` isn't set up for email on this app, or the app doesn't exist."},"422":{"description":"The request body is missing a required field or has an invalid value."}}},"get":{"summary":"List email domains","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nLists the app's email domains and where each one is in setup.\n\nAn app with no email set up returns an empty list rather than an error, so this is the safe endpoint to poll while a domain verifies.\n\nYou normally get one domain. During a replacement you get two, the old one still sending and the new one still verifying. Disabled and suspended domains aren't listed, so unlinking a custom domain drops its email domain from this list until you link the domain again.\n\nEvery entry repeats the same `id`, which identifies the app's email configuration rather than the individual domain. Tell entries apart by `domain`, and pass that value to the endpoints that take a `domain` parameter.","operationId":"list_email_domains_api_apps__app_id__custom_email_domains_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose email domains you want to work with.","title":"App Id"},"description":"ID of the app whose email domains you want to work with.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The app's email domains. Empty when none are set up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEmailDomainsResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"App not found."}}},"delete":{"summary":"Disable email sending for a domain","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nTurns off email sending for a domain.\n\nThe domain itself stays connected to the app and keeps serving the site. Only the ability to send mail from it goes away, and mail falls back to Base44's own sending address.\n\nBase44 also tries to remove the domain from the mail provider, but a successful call doesn't promise that part succeeded. If the provider can't be reached, the local configuration is still removed and the call still succeeds.\n\nThis removes the domain's email setup rather than pausing it. To send from the domain again you run [Enable email sending for a domain](/api-reference/enable-email-sending-for-a-domain), publish DNS records, and wait for verification, the same as the first time.\n\nThe response body is empty, so read the status code.","operationId":"delete_email_domain_endpoint_api_apps__app_id__custom_email_domains_delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose email domains you want to work with.","title":"App Id"},"description":"ID of the app whose email domains you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"domain","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Domain to delete. Required when multiple domains exist.","title":"Domain"},"description":"Domain to delete. Required when multiple domains exist."}],"responses":{"200":{"description":"Email sending is off for this domain. The body is empty.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteEmailResponse"}}}},"400":{"description":"This app has more than one email domain and you didn't say which to act on. Send `domain`."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, you used a workspace API key, or the email domain is suspended. A suspended domain says why in the message."},"404":{"description":"This app has no email domain set up, or none matching the `domain` you sent."}}}},"/api/apps/{app_id}/custom-email-domains/replace":{"post":{"summary":"Replace the email domain","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nMoves the app's email sending to a different domain it already owns.\n\nA current domain that is already sending keeps sending until the new one verifies, so mail keeps going out while DNS propagates. A current domain that never finished setup isn't sending, and nothing sends until the new domain verifies. During the changeover [List email domains](/api-reference/list-email-domains) returns both.\n\nThe new domain has the same requirements as [Enable email sending for a domain](/api-reference/enable-email-sending-for-a-domain). It has to be connected, ready, and different from the current one. Sending the domain already in use is rejected.\n\nThis works only when the app has exactly one email domain. A replacement that's still in flight leaves two, and this call is rejected until that clears. Base44 tries to remove the old domain once the new one verifies, but that doesn't happen on every path and can fail quietly. When a replacement is rejected, read [List email domains](/api-reference/list-email-domains) and clear the extra domain with [Disable email sending for a domain](/api-reference/disable-email-sending-for-a-domain).","operationId":"replace_email_domain_api_apps__app_id__custom_email_domains_replace_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose email domains you want to work with.","title":"App Id"},"description":"ID of the app whose email domains you want to work with.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplaceEmailDomainRequest"}}}},"responses":{"200":{"description":"The new domain's setup started. The old one keeps sending until it verifies.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplaceEmailDomainResponse"}}}},"400":{"description":"The new domain can't send mail, is the one already in use, or the app has more than one email domain."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"This workspace's plan doesn't include custom domains."},"403":{"description":"You don't have access to this app, you used a workspace API key, or the email domain is suspended. A suspended domain says why in the message."},"404":{"description":"The new domain isn't connected to this app, the app has no email domain to replace, or the app doesn't exist."},"412":{"description":"The domain isn't ready yet. It still needs verifying, or its DNS hasn't finished propagating."},"422":{"description":"The request body is missing a required field or has an invalid value."},"429":{"description":"Rate limit exceeded. The base limit is 10 requests per minute. See [Rate limits](/developers/references/app-management/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/custom-email-domains/configure":{"post":{"summary":"Retry email domain setup","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRetries setup for a domain that stalled, and reports where it got to.\n\nWhat it retries depends on where the domain stopped:\n\n- A domain waiting on DNS gets its records written again when Base44 controls them, or is reset so you can publish them yourself.\n- A domain whose provider registration failed is registered again.\n- A domain waiting on verification is checked again.\n\nThe `active` and `not_configured` statuses can't be retried. There's nothing to retry on a domain that already sends, and one that was never set up needs [Enable email sending for a domain](/api-reference/enable-email-sending-for-a-domain) instead.","operationId":"configure_email_domain_api_apps__app_id__custom_email_domains_configure_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose email domains you want to work with.","title":"App Id"},"description":"ID of the app whose email domains you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"domain","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Domain to configure. Required when multiple domains exist.","title":"Domain"},"description":"Domain to configure. Required when multiple domains exist."}],"responses":{"200":{"description":"Where setup stands after the retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfigureEmailResponse"}}}},"400":{"description":"This app has more than one email domain and you didn't say which to act on. Send `domain`."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, you used a workspace API key, or the email domain is suspended. A suspended domain says why in the message."},"404":{"description":"This app has no email domain set up, or none matching the `domain` you sent."},"412":{"description":"There's nothing to retry. The domain already sends, or it was never set up."},"429":{"description":"Rate limit exceeded. The base limit is 5 requests per minute. See [Rate limits](/developers/references/app-management/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/custom-domains/{domain_id}":{"get":{"summary":"Get custom domain","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns one custom domain by ID.\n\nThis reads Base44's stored copy of the domain and never contacts the hosting provider, so `last_status_check` and `last_status_payload` are only as current as the last status check that ran. Use [Get custom domain status](/api-reference/get-custom-domain-status) when you need the provider's answer now.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"get_domain_api_apps__app_id__custom_domains__domain_id__get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the domain belongs to.","title":"App Id"},"description":"ID of the app the domain belongs to.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the custom domain. Get this from [List custom domains](/api-reference/list-custom-domains).","title":"Domain Id"},"description":"ID of the custom domain. Get this from [List custom domains](/api-reference/list-custom-domains).","example":"68b1c0d4e7b91d003c45a1f7"}],"responses":{"200":{"description":"The domain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomDomainResource"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"The app has no custom domain with this ID, or the app doesn't exist."}}},"delete":{"summary":"Delete custom domain","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRemoves a custom domain from the app and stops serving it.\n\nThis detaches the domain from Base44's hosting, deletes any email domain configured on it, and stops it serving your app. Other domains that were redirecting to this one stop redirecting.\n\nDeleting is permanent and there's no undo. The domain itself isn't affected at your registrar, so you can point it somewhere else afterwards, but its Base44 configuration is removed. To stop serving a domain while keeping it attached, use [Unlink custom domain](/api-reference/unlink-custom-domain) instead.\n\nA failed delete can still take the domain out of service. Base44 detaches it from hosting and removes its email domain first, so a failure after that point leaves a domain that no longer works but still appears in [List custom domains](/api-reference/list-custom-domains). Retrying is safe and is how you finish the job. If the domain has already been deleted, the retry reports it as missing.","operationId":"delete_domain_api_apps__app_id__custom_domains__domain_id__delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the domain belongs to.","title":"App Id"},"description":"ID of the app the domain belongs to.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the custom domain. Get this from [List custom domains](/api-reference/list-custom-domains).","title":"Domain Id"},"description":"ID of the custom domain. Get this from [List custom domains](/api-reference/list-custom-domains).","example":"68b1c0d4e7b91d003c45a1f7"}],"responses":{"200":{"description":"The domain is deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainMessageResponse"}}}},"400":{"description":"The domain could not be detached from Base44's hosting. The domain is left attached, so retry."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"The app has no custom domain with this ID, or the app doesn't exist."}}}},"/api/apps/{app_id}/custom-domains":{"get":{"summary":"List custom domains","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns every custom domain attached to the app.\n\nThe list covers domains at any stage:\n\n- Added, but not linked\n- Linked, but not yet verified\n- Live\n- Redirecting to another domain\n- Disabled\n\nAdding a domain and linking it are separate steps. DNS propagation happens outside Base44, so a linked domain can sit unverified for as long as its nameservers take.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"list_domains_api_apps__app_id__custom_domains_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the domain belongs to.","title":"App Id"},"description":"ID of the app the domain belongs to.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The app's custom domains.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CustomDomainResource"},"title":"CustomDomains"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"App not found."}}}},"/api/apps/{app_id}/custom-domains/{domain_id}/link":{"post":{"summary":"Link custom domain","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nAttaches the domain to Base44's hosting and starts DNS verification.\n\nCall this after adding a domain. It's also how you put a domain back into service after [Unlink custom domain](/api-reference/unlink-custom-domain). Linking re-enables any email domain configured on it.\n\nThe response body is `null`, so read the domain back with [Get custom domain](/api-reference/get-custom-domain). Verification doesn't finish here. Linking only asks the provider to start checking DNS, so poll [Get custom domain status](/api-reference/get-custom-domain-status) until `verificationStatus` is `verified`.\n\nDNS propagation happens outside Base44, so a linked domain can sit unverified for as long as its nameservers take.","operationId":"link_to_render_api_apps__app_id__custom_domains__domain_id__link_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the domain belongs to.","title":"App Id"},"description":"ID of the app the domain belongs to.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the custom domain. Get this from [List custom domains](/api-reference/list-custom-domains).","title":"Domain Id"},"description":"ID of the custom domain. Get this from [List custom domains](/api-reference/list-custom-domains).","example":"68b1c0d4e7b91d003c45a1f7"}],"responses":{"200":{"description":"The domain is attached and verification has started. The body is `null`.","content":{"application/json":{"schema":{}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"The app has no custom domain with this ID, or the app doesn't exist."},"429":{"description":"Base44's hosting provider is rate limiting. Retry later."}}}},"/api/apps/{app_id}/custom-domains/{domain_id}/status":{"post":{"summary":"Get custom domain status","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nAsks the hosting provider whether the domain's DNS is verified, and returns the domain with the answer written into it.\n\nThis is the endpoint to poll while waiting for a domain to go live. It reaches the provider on every call, unlike [Get custom domain](/api-reference/get-custom-domain), which returns Base44's stored copy. It also writes what it learns, so `last_status_check` and `last_status_payload` on the domain are updated as a side effect.\n\nOnly call this for a domain that's currently linked. A domain that was never linked, or was unlinked, isn't attached at the provider, and asking about it fails rather than reporting \"not verified\".\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"get_status_api_apps__app_id__custom_domains__domain_id__status_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the domain belongs to.","title":"App Id"},"description":"ID of the app the domain belongs to.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the custom domain. Get this from [List custom domains](/api-reference/list-custom-domains).","title":"Domain Id"},"description":"ID of the custom domain. Get this from [List custom domains](/api-reference/list-custom-domains).","example":"68b1c0d4e7b91d003c45a1f7"}],"responses":{"200":{"description":"The domain, with the provider's answer written into its status fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomDomainResource"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"The app has no custom domain with this ID, or the app doesn't exist."}}}},"/api/apps/{app_id}/custom-domains/{domain_id}/unlink":{"post":{"summary":"Unlink custom domain","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nStops serving the domain, keeping it attached to the app.\n\nAny email domain on it is disabled, and other domains redirecting to this one stop redirecting. The domain stays attached to the app and keeps its redirect target, so [Link custom domain](/api-reference/link-custom-domain) puts it back into service. To remove it altogether, use [Delete custom domain](/api-reference/delete-custom-domain).\n\nA successful call doesn't guarantee the domain has stopped working. Base44 also has to remove it from the system that routes visitors to your app, and that step can fail without failing the call. Nothing in the response reports it, so check the domain in a browser, and unlink again if it still serves the app.","operationId":"unlink_render_connection_api_apps__app_id__custom_domains__domain_id__unlink_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the domain belongs to.","title":"App Id"},"description":"ID of the app the domain belongs to.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the custom domain. Get this from [List custom domains](/api-reference/list-custom-domains).","title":"Domain Id"},"description":"ID of the custom domain. Get this from [List custom domains](/api-reference/list-custom-domains).","example":"68b1c0d4e7b91d003c45a1f7"}],"responses":{"200":{"description":"The domain is no longer served.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainMessageResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"The app has no custom domain with this ID, or the app doesn't exist."}}}},"/api/apps/{app_id}/custom-domains/{domain_id}/verify":{"post":{"summary":"Verify custom domain","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nAsks the hosting provider to check the domain's DNS.\n\nVerification normally starts on its own when you link a domain. Call this when you have just fixed the DNS records and don't want to wait for the next automatic check.\n\nA successful call means the check was accepted, not that the domain is verified. Poll [Get custom domain status](/api-reference/get-custom-domain-status) for the result.\n\nA domain whose DNS doesn't point to Base44 yet fails in a way this endpoint doesn't document. Treat any failure here as \"the check didn't start\", fix the DNS records, and call this again.","operationId":"verify_domain_api_apps__app_id__custom_domains__domain_id__verify_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the domain belongs to.","title":"App Id"},"description":"ID of the app the domain belongs to.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the custom domain. Get this from [List custom domains](/api-reference/list-custom-domains).","title":"Domain Id"},"description":"ID of the custom domain. Get this from [List custom domains](/api-reference/list-custom-domains).","example":"68b1c0d4e7b91d003c45a1f7"}],"responses":{"200":{"description":"The provider accepted the verification request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainMessageResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"The app has no custom domain with this ID, or the app doesn't exist."}}}},"/api/apps/{app_id}/url-redirects":{"get":{"summary":"List URL redirects","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns every URL redirect on the app's published site.\n\nEach rule sends visitors from one path to another with a 301, which search engines treat as permanent.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"list_url_redirects_api_apps__app_id__url_redirects_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose URL redirects you want to work with.","title":"App Id"},"description":"ID of the app whose URL redirects you want to work with.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The app's URL redirects.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UrlRedirectResource"},"title":"UrlRedirects"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"App not found."}}},"post":{"summary":"Create URL redirect","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nAdds a 301 redirect to the app's published site.\n\nUse it to keep old links working after you rename or remove a page. The rule takes effect on the published site once this call returns.\n\nBase44 also tries to drop any cached copy of the source path so visitors get the redirect straight away, but that step can fail without failing the call. When it does, the source URL keeps serving its cached page with a 200 instead of the new 301 until the cache refreshes on its own. A `prefix` rule affects child paths too, and each one has its own cached copy. Base44 drops the copies it knows about, and any it doesn't keep serving their old page until that cache expires.\n\nA rule is rejected when:\n\n- Its source is a path the redirect layer never sees, which covers `/sitemap.xml`, `/robots.txt`, `/favicon.ico`, `/manifest.json`, `/link_preview.png`, `/llms.txt`, `/.well-known/*`, and the app's auth paths\n- Its source and target are the same\n- It's a `prefix` rule starting at `/`\n- It overlaps another rule, so a `prefix` rule at `/docs` blocks a `single` rule at `/docs/intro`\n- It would make a visitor follow two redirects in a row, which happens when an internal target matches another rule's source\n\nAn app can hold up to 50 rules, and the 51st is rejected.\n\nURL redirects are part of custom domains, so creating, updating and deleting one needs a workspace whose plan includes them. Listing them doesn't.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"create_url_redirect_api_apps__app_id__url_redirects_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose URL redirects you want to work with.","title":"App Id"},"description":"ID of the app whose URL redirects you want to work with.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UrlRedirectPayload"}}}},"responses":{"200":{"description":"The redirect that was created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UrlRedirectResource"}}}},"400":{"description":"The app already has 50 URL redirects."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"This workspace's plan doesn't include custom domains."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"App not found."},"422":{"description":"The request body is invalid, or the redirect breaks one of the rules above. The message names what failed."}}}},"/api/apps/{app_id}/url-redirects/{redirect_id}":{"put":{"summary":"Update URL redirect","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReplaces a redirect's source, target and match type.\n\nSend the whole rule. Every field is applied, not merged, and the same rules as [Create URL redirect](/api-reference/create-url-redirect) apply. If the new rule conflicts with another one the redirect is left exactly as it was.\n\nChanging `source_path` tries to clear the cached copy of both the old and the new path. That can fail without failing the call, and an old source whose cache survives keeps serving the previous target's page until the cache refreshes.\n\nURL redirects are part of custom domains, so creating, updating and deleting one needs a workspace whose plan includes them. Listing them doesn't.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"update_url_redirect_api_apps__app_id__url_redirects__redirect_id__put","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose URL redirects you want to work with.","title":"App Id"},"description":"ID of the app whose URL redirects you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"redirect_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the redirect. Get this from [List URL redirects](/api-reference/list-url-redirects).","title":"Redirect Id"},"description":"ID of the redirect. Get this from [List URL redirects](/api-reference/list-url-redirects).","example":"68c2d1e5f3b8a4216e9b5583"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UrlRedirectPayload"}}}},"responses":{"200":{"description":"The updated redirect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UrlRedirectResource"}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"This workspace's plan doesn't include custom domains."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"The app has no URL redirect with this ID, or the app doesn't exist."},"422":{"description":"The request body is invalid, or the redirect breaks one of the rules above. The message names what failed."}}},"delete":{"summary":"Delete URL redirect","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRemoves a redirect from the app's published site.\n\nThe source path goes back to serving whatever the app has at it, or a 404 when it has nothing.\n\nBase44 tries to clear the cached copy so the change takes effect right away, but that can fail without failing the call. When it does, the old path keeps serving the target's cached page instead of redirecting to it, until the cache refreshes. That looks like the redirect still works, so check the status code rather than the page.\n\nThe response body is empty, so read the status code.\n\nURL redirects are part of custom domains, so creating, updating and deleting one needs a workspace whose plan includes them. Listing them doesn't.","operationId":"delete_url_redirect_api_apps__app_id__url_redirects__redirect_id__delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose URL redirects you want to work with.","title":"App Id"},"description":"ID of the app whose URL redirects you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"redirect_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the redirect. Get this from [List URL redirects](/api-reference/list-url-redirects).","title":"Redirect Id"},"description":"ID of the redirect. Get this from [List URL redirects](/api-reference/list-url-redirects).","example":"68c2d1e5f3b8a4216e9b5583"}],"responses":{"204":{"description":"The redirect was removed."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"This workspace's plan doesn't include custom domains."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"The app has no URL redirect with this ID, or the app doesn't exist."}}}},"/api/apps/platform/{app_id}/published-url":{"get":{"summary":"Get published URL","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the public URL of a published app, so you can link to it or open it.\n\nThe app has to be live. An app that has never been published, or that has been unpublished, has no public URL. Publish it with [Deploy an app](/api-reference/deploy-an-app) first.\n\nThe URL is always built from the app's slug, like `https://my-crm-3c45a1f2.base44.app`. If you also serve the app on your own domain, that domain isn't returned, and the slug URL keeps working alongside it.","operationId":"get_app_published_url_api_apps_platform__app_id__published_url_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose published URL to return.","title":"App Id"},"description":"ID of the app whose published URL to return.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishedUrlResponse"}}}},"401":{"description":"Missing or invalid credentials."},"404":{"description":"App not found, or it isn't currently published."}}}},"/api/apps":{"post":{"summary":"Create app","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nCreates a new app. Pass `initial_message.content` to build it from a prompt, or send an empty body (`{}`) to create an empty app. Add `initial_message.file_urls` to build from a screenshot or a mockup alongside the prompt.\n\nBuilding from a prompt runs in the background and consumes credits. Poll [Get app](/api-reference/get-app) and watch its `status` to see when the build finishes. By default the app is created in your default workspace. Set `organization_id` to create it in another workspace you belong to. This endpoint is limited to 5 requests per minute.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"api_create_api_apps_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"CreateApp","properties":{"initial_message":{"type":"object","description":"First prompt to build the app from. Omit to create an empty app.","properties":{"content":{"type":"string","description":"Prompt describing the app to build.","example":"A CRM to track leads and deals"},"file_urls":{"type":"array","items":{"type":"string"},"description":"Publicly reachable URLs of files to build the app from, such as a screenshot or a mockup to copy the layout from. Base44 downloads each file, so a URL has to resolve without credentials. A URL Base44 cannot fetch does not fail this request. The app is still created, and the failure shows up afterwards as a `status.state` of `error`.","example":["https://example.com/mockup.png"]}}},"organization_id":{"type":"string","description":"ID of the workspace to create the app in. Omit to use your default workspace. You must have an editor-capable role (Editor or above) in the workspace. Viewers and guests cannot create apps. This is the same workspace ID that [List apps](/api-reference/list-apps) accepts as `workspace_id`.","example":"67e0b12c4d8a3f005b21c9e4"}}},"example":{"initial_message":{"content":"A CRM to track leads and deals"}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSummary"}}}},"400":{"description":"The request body is invalid."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't create apps in this workspace, because you are not a member or your role is not editor-capable."},"422":{"description":"The request body is missing, or is not a JSON object."},"429":{"description":"Rate limit exceeded (5 requests per minute)."}}},"get":{"summary":"List apps","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the apps you can access in a given workspace. As a workspace member you get the apps you own or collaborate on. As a workspace admin you get every app in the workspace.\n\nThe workspace defaults to your personal workspace. Set `workspace_id` to list a different workspace you belong to.\n\nPass `fields` to receive only the properties you need.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"api_list_api_apps_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AppSummary"},"title":"Apps"}}}},"400":{"description":"The `limit` is outside 1 to 10000, `skip` is negative, or `sort` names more than one field."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You are not a member of the workspace you asked for."}},"parameters":[{"name":"workspace_id","in":"query","required":false,"description":"ID of the workspace to list apps from. Omit to use your personal workspace. You must be a member of the workspace you request. Each request lists the apps in a single workspace.","schema":{"type":"string"},"example":"67e0b12c4d8a3f005b21c9e4"},{"name":"fields","in":"query","required":false,"description":"Comma-separated list of fields to return. For example, `id,name,slug,status`. Omit to receive the full app document.","schema":{"type":"string"},"example":"id,name,slug,status"},{"name":"sort","in":"query","required":false,"description":"Single field to sort by, prefixed with `-` for descending. For example, `-created_date` returns newest first.","schema":{"type":"string"},"example":"-created_date"},{"name":"limit","in":"query","required":false,"description":"Maximum number of apps to return. Omit to return all the apps you can access.","schema":{"type":"integer","minimum":1,"maximum":10000},"example":50},{"name":"skip","in":"query","required":false,"description":"Number of apps to skip before the page starts. Combine with `limit` to page through results.","schema":{"type":"integer","minimum":0},"example":0}]}},"/api/apps/{app_id}":{"get":{"summary":"Get app","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the specified app. As a workspace member you can get an app you own or collaborate on. As a workspace admin you can get any app in the workspace.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"api_get_api_apps__app_id__get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app.","title":"App Id"},"description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSummary"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or it doesn't exist."}}}},"/api/apps/{app_id}/workflows":{"post":{"summary":"Create workflow","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nCreates a workflow and starts it running.\n\nThe new workflow is active immediately, so a scheduled trigger begins firing on its schedule and an event trigger starts listening as soon as this returns. Create it, then call [Toggle workflow status](/api-reference/toggle-workflow-status) if you want it paused instead.\n\nNames are unique per app across everything that is not archived. Reusing a name returns a 409, so update the existing workflow with [Update workflow](/api-reference/update-workflow) rather than creating a second one. Saving also writes a matching file into the app's code, so the workflow shows up in the editor alongside everything else.\n\nThis endpoint is limited to 20 requests per minute.\n\n<Note>A workflow is a definition plus a trigger. The definition is a CNCF Serverless Workflow v1.0 document describing the steps to run, and the trigger decides when they run. Both are free-form objects here, so check a definition with [Validate a workflow definition](/api-reference/validate-a-workflow-definition) before you save it.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"create_workflow_api_apps__app_id__workflows_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkflowRequest"}}}},"responses":{"200":{"description":"The created workflow.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowResponse"}}}},"409":{"description":"Another workflow on this app already uses this name."},"422":{"description":"The definition or trigger is not valid. The body lists what is wrong."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"This workspace's plan does not include workflows. Upgrade to Builder or above."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."}}},"get":{"summary":"List workflows","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nLists the app's workflows.\n\nArchived workflows are left out unless you set `include_archived`. Use `limit` and `offset` to page through the results.\n\nPassing `file_keys` switches this to a lookup by file name and ignores `limit` and `offset` entirely, returning every match. That is the only way to fetch more than 200 workflows in one call, and it is meant for resolving names you already hold rather than for paging.\n\nThe response omits each workflow's definition. Read one workflow with [Get workflow](/api-reference/get-workflow) when you need it.\n\nThis endpoint is limited to 60 requests per minute.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"list_workflows_api_apps__app_id__workflows_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"include_archived","in":"query","required":false,"schema":{"type":"boolean","description":"Include archived workflows in the results.","default":false,"title":"Include Archived"},"description":"Include archived workflows in the results.","example":false},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Most workflows to return.","default":30,"title":"Limit"},"description":"Most workflows to return.","example":30},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"How many workflows to skip, for paging.","default":0,"title":"Offset"},"description":"How many workflows to skip, for paging.","example":0},{"name":"file_keys","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Look workflows up by their file name instead of paging. Repeat the parameter for several. At most 50, each at most 256 characters and free of path separators.","title":"File Keys"},"description":"Look workflows up by their file name instead of paging. Repeat the parameter for several. At most 50, each at most 256 characters and free of path separators.","example":["email-me-new-signups"]}],"responses":{"200":{"description":"The app's workflows.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowListItem"},"title":"Workflows"}}}},"422":{"description":"`file_keys` has more than 50 entries, or one is longer than 256 characters or contains a path separator."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."}}}},"/api/apps/{app_id}/workflows/runs":{"get":{"summary":"List workflow runs for an app","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nLists runs across every workflow on the app, newest first.\n\nUse `status` to keep only runs in one state, and `since` to keep only runs that started after a moment in time. Page with `limit` and `offset`.\n\nEach row names its workflow, so this is the endpoint for an activity feed. For one workflow's history use [List runs for a workflow](/api-reference/list-runs-for-a-workflow).\n\nThis endpoint is limited to 60 requests per minute.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"list_app_runs_api_apps__app_id__workflows_runs_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Most runs to return.","default":30,"title":"Limit"},"description":"Most runs to return.","example":30},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"How many runs to skip, for paging.","default":0,"title":"Offset"},"description":"How many runs to skip, for paging.","example":0},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Keep only runs in this state, for example `failed`.","title":"Status"},"description":"Keep only runs in this state, for example `failed`.","example":"failed"},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Keep only runs that started at or after this ISO 8601 datetime.","title":"Since"},"description":"Keep only runs that started at or after this ISO 8601 datetime.","example":"2026-08-01T00:00:00Z"}],"responses":{"200":{"description":"Runs across the app's workflows, newest first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RunItem"},"title":"WorkflowRuns"}}}},"400":{"description":"`since` is not an ISO 8601 datetime."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/workflows/suggestions":{"get":{"summary":"Suggest workflows","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSuggests workflows worth building for this app.\n\nEach suggestion carries a `prompt` you can send straight to [Send chat message](/api-reference/send-chat-message) to have the builder create it. Nothing is saved by calling this.\n\n`page` picks between two different sets, so `0` and `1` give you meaningfully different ideas. Anything higher is treated as the last page rather than rejected. Results are cached per app and page, so asking twice returns the same set.\n\n`suggestions` can come back empty when the model returns nothing usable, under a 200.\n\nThis endpoint is limited to 5 requests per minute.","operationId":"get_workflow_suggestions_api_apps__app_id__workflows_suggestions_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Which set of ideas to return. `0` and `1` give different sets; anything higher returns the last set.","default":0,"title":"Page"},"description":"Which set of ideas to return. `0` and `1` give different sets; anything higher returns the last set.","example":0}],"responses":{"200":{"description":"Workflow ideas for this app.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowSuggestions"}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"This workspace's plan does not include workflows. Upgrade to Builder or above."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/workflows/stats":{"get":{"summary":"Get workflow stats","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nCounts each workflow's runs over a time window, for a health view.\n\nOne row per workflow that ran in the window, with how many runs finished, failed, or were cancelled, and how long they took on average. A workflow with no runs in the window does not appear.\n\nThe window defaults to the last 24 hours. Set `since` and `until` to choose your own. A window longer than 30 days is not rejected: `since` is moved forward so the window ends at `until` and covers the 30 days before it, and nothing in the response says that happened. Ask for at most 30 days if you need the range you sent to be the range you get.\n\nThis endpoint is limited to 30 requests per minute.","operationId":"get_workflow_stats_api_apps__app_id__workflows_stats_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start of the window, as an ISO 8601 datetime. Defaults to 24 hours ago.","title":"Since"},"description":"Start of the window, as an ISO 8601 datetime. Defaults to 24 hours ago.","example":"2026-08-01T00:00:00Z"},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End of the window, as an ISO 8601 datetime. Defaults to now.","title":"Until"},"description":"End of the window, as an ISO 8601 datetime. Defaults to now.","example":"2026-08-25T00:00:00Z"}],"responses":{"200":{"description":"One row per workflow that ran in the window.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowStatsRow"},"title":"WorkflowStats"}}}},"400":{"description":"`since` or `until` is not an ISO 8601 datetime."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."}}}},"/api/apps/{app_id}/workflows/{workflow_id}":{"get":{"summary":"Get workflow","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReads one workflow, including the full definition it currently runs.\n\nThis is the only endpoint that returns `definition`. The list endpoint leaves it out.\n\nThis endpoint is limited to 60 requests per minute.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"get_workflow_api_apps__app_id__workflows__workflow_id__get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","title":"Workflow Id"},"description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","example":"68b1c0d4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The workflow, including its current definition.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"There is no workflow with this ID on this app."}}},"put":{"summary":"Update workflow","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nUpdates a workflow. Send only the fields you want to change.\n\nAnything you leave out keeps its stored value, so a rename is just `{\"name\": \"...\"}`. Changing `definition` saves a new immutable version and the workflow runs it from then on; earlier versions stay readable through [List workflow versions](/api-reference/list-workflow-versions). Pass `change_summary` to label what changed.\n\nRenaming to a name another live workflow already uses returns a 409. An invalid definition returns a 422 with the validation errors, and nothing is saved.\n\nUpdating does not change whether the workflow is running. It also rewrites the workflow's file in the app's code.\n\nThis endpoint is limited to 30 requests per minute.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>","operationId":"update_workflow_api_apps__app_id__workflows__workflow_id__put","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","title":"Workflow Id"},"description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","example":"68b1c0d4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWorkflowRequest"}}}},"responses":{"200":{"description":"The updated workflow.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowResponse"}}}},"409":{"description":"Another workflow on this app already uses this name."},"422":{"description":"The definition or trigger is not valid. The body lists what is wrong."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"This workspace's plan does not include workflows. Upgrade to Builder or above."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"There is no workflow with this ID on this app."}}},"delete":{"summary":"Archive workflow","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nArchives a workflow so it stops running.\n\nArchiving cancels its schedule, stops it responding to its trigger, and removes its file from the app's code. The workflow and its run history are kept, and [Restore workflow](/api-reference/restore-workflow) brings it back.\n\nArchiving frees the workflow's name, so another workflow can take it while this one is archived. That is what makes a later restore fail with a 409.\n\nThis endpoint is limited to 20 requests per minute.","operationId":"archive_workflow_api_apps__app_id__workflows__workflow_id__delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","title":"Workflow Id"},"description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","example":"68b1c0d4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The workflow is archived.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArchiveResponse"}}}},"422":{"description":"The workflow could not be archived. The body says why."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"There is no workflow with this ID on this app."}}}},"/api/apps/{app_id}/workflows/{workflow_id}/unarchive":{"post":{"summary":"Restore workflow","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRestores an archived workflow and tries to start it again.\n\nRead `status` on the response rather than assuming the workflow is running. A restored workflow lands `inactive` rather than `active` when it cannot start, which happens when its schedule has already ended, something it references was deleted, or a connector is not ready. `message` says which, in words you can show someone.\n\nRestoring fails with a 409 when another live workflow has taken this one's name or file name since it was archived. Rename the other workflow, or rename this one on the way back, before retrying.\n\nIf the workflow's file cannot be written back into the app's code, the workflow is left archived rather than half-restored, and the call reports the failure.\n\nThis endpoint is limited to 20 requests per minute.","operationId":"unarchive_workflow_api_apps__app_id__workflows__workflow_id__unarchive_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","title":"Workflow Id"},"description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","example":"68b1c0d4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The workflow is restored. Read `status` for whether it started.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToggleStatusResponse"}}}},"409":{"description":"Another workflow has taken this one's name or file name since it was archived."},"422":{"description":"The workflow could not be restored. The body says why."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"This workspace's plan does not include workflows. Upgrade to Builder or above."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"There is no workflow with this ID on this app."}}}},"/api/apps/{app_id}/workflows/{workflow_id}/toggle-status":{"post":{"summary":"Toggle workflow status","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nFlips a workflow between running and paused.\n\nThis flips whatever the current status is, it does not set a status you choose. Read the workflow first with [Get workflow](/api-reference/get-workflow) and only call this when `status` is not already what you want, or two calls in a row will land you back where you started.\n\nPausing takes effect immediately: the schedule is removed and the trigger stops firing. Starting a workflow again requires a plan that includes workflows; pausing one does not, so a workspace that has lost the capability can still stop a workflow it can no longer start.\n\nArchived workflows cannot be toggled and return a 400. Restore one first with [Restore workflow](/api-reference/restore-workflow).\n\nThis endpoint is limited to 20 requests per minute.","operationId":"toggle_workflow_status_api_apps__app_id__workflows__workflow_id__toggle_status_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","title":"Workflow Id"},"description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","example":"68b1c0d4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The workflow's new status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToggleStatusResponse"}}}},"400":{"description":"The workflow is archived, so it cannot be toggled."},"422":{"description":"The status could not be changed. The body says why."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"This workspace's plan does not include workflows. Upgrade to Builder or above."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"There is no workflow with this ID on this app."}}}},"/api/apps/{app_id}/workflows/validate-definition":{"post":{"summary":"Validate a workflow definition","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nChecks a workflow definition and tells you what is wrong with it, without saving anything.\n\nCall this before [Create workflow](/api-reference/create-workflow) or [Update workflow](/api-reference/update-workflow) to catch problems while you still have the definition in hand. An invalid definition comes back as a 200 with `valid` set to `false` and the problems in `errors`, not as an error status, so branch on `valid` rather than on the status code.\n\nThe response also lists the task types and activities this app can use, which is the set a valid definition has to stay inside.\n\nThis endpoint is limited to 30 requests per minute.","operationId":"validate_definition_api_apps__app_id__workflows_validate_definition_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateDefinitionRequest"}}}},"responses":{"200":{"description":"The verdict. Check `valid`, not the status code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationResponse"}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"This workspace's plan does not include workflows. Upgrade to Builder or above."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/workflows/{workflow_id}/versions":{"get":{"summary":"List workflow versions","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nLists a workflow's saved versions, newest first.\n\nEvery definition change saves a version, so this is the history of what the workflow has run. Read one with [Get workflow version](/api-reference/get-workflow-version).\n\n`limit` caps at 200 and there is no paging, so a workflow edited more than 200 times returns only its most recent 200 versions with nothing in the response saying so.\n\nThis endpoint is limited to 30 requests per minute.","operationId":"list_versions_api_apps__app_id__workflows__workflow_id__versions_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","title":"Workflow Id"},"description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","example":"68b1c0d4e7b91d003c45a1f2"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Most versions to return.","default":30,"title":"Limit"},"description":"Most versions to return.","example":30}],"responses":{"200":{"description":"The workflow's versions, newest first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/VersionItem"},"title":"WorkflowVersions"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"There is no workflow with this ID on this app."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/workflows/{workflow_id}/versions/{version_id}":{"get":{"summary":"Get workflow version","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReads one saved version's full definition.\n\nUse it to see the definition a past run actually executed, which is not always the current one. Each run reports the version it ran as `definition_version_id` on [Get workflow run](/api-reference/get-workflow-run).\n\nThis endpoint is limited to 30 requests per minute.","operationId":"get_version_api_apps__app_id__workflows__workflow_id__versions__version_id__get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","title":"Workflow Id"},"description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","example":"68b1c0d4e7b91d003c45a1f2"},{"name":"version_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the version, as returned in `version_id` by [List workflow versions](/api-reference/list-workflow-versions). It is the SHA-256 hash of the definition, not a Base44 object ID.","title":"Version Id"},"description":"ID of the version, as returned in `version_id` by [List workflow versions](/api-reference/list-workflow-versions). It is the SHA-256 hash of the definition, not a Base44 object ID.","example":"9f2c1a7b3e5d84f60c1b2a9e7d4f8c3b6a5e2d1f0c9b8a7e6d5c4b3a2f1e0d9c"}],"responses":{"200":{"description":"The version and its definition.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionDefinitionResponse"}}}},"404":{"description":"There is no workflow with this ID on this app, or no version with this ID on that workflow."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key."}}}},"/api/apps/{app_id}/workflows/{workflow_id}/run-now-info":{"get":{"summary":"Get workflow run-now options","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nTells you what [Run a workflow now](/api-reference/run-a-workflow-now) needs for this workflow.\n\nWhen `manual_run_config.requires_previous_run` is `false` the workflow runs with an empty payload and you can call run-now with an empty body. When it is `true` the trigger's payload refers to real data in the app, so a manual run has to replay a previous run's payload and you pass one of the `recent_runs` IDs as `replay_from_run_id`.\n\nEvery run listed in `recent_runs` is known to be replayable, so a run-now using one should not be refused. The list holds at most 20 runs and is empty when the trigger needs no payload.\n\nThis endpoint is limited to 30 requests per minute.","operationId":"get_run_now_info_api_apps__app_id__workflows__workflow_id__run_now_info_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","title":"Workflow Id"},"description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","example":"68b1c0d4e7b91d003c45a1f2"}],"responses":{"200":{"description":"What a manual run needs, and which runs can be replayed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunNowInfoResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"There is no workflow with this ID on this app."}}}},"/api/apps/{app_id}/workflows/{workflow_id}/run-now":{"post":{"summary":"Run a workflow now","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRuns a workflow immediately, without waiting for its trigger.\n\nCheck [Get workflow run-now options](/api-reference/get-workflow-run-now-options) first. If that reports `requires_previous_run`, pass one of the run IDs it offers as `replay_from_run_id` and the workflow runs again with that run's payload. Otherwise send an empty body.\n\nThis executes the workflow for real: it calls whatever the definition calls and consumes credits. Runs started this way are recorded with `is_test_run` set to `true`, which is what keeps them out of the replay list on later calls.\n\nThe call returns as soon as the run is queued, so `status` is `started` rather than a result. Poll [Get workflow run](/api-reference/get-workflow-run) with the `run_id` to watch it finish.\n\nArchived workflows cannot be run and return a 400. A `replay_from_run_id` whose payload no longer matches the workflow's current trigger returns a 409, because replaying it would run the steps against fields that have since moved.\n\nThis endpoint is limited to 5 requests per minute.","operationId":"run_now_workflow_api_apps__app_id__workflows__workflow_id__run_now_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","title":"Workflow Id"},"description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","example":"68b1c0d4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunNowRequest"}}}},"responses":{"200":{"description":"The run is queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunNowResponse"}}}},"400":{"description":"The workflow is archived, or its trigger needs `replay_from_run_id` and you did not send one."},"409":{"description":"The run you asked to replay was started by a different trigger configuration, or its payload is no longer available."},"404":{"description":"There is no workflow with this ID on this app, or no run with the ID you asked to replay."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"This workspace's plan does not include workflows. Upgrade to Builder or above."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/workflows/{workflow_id}/runs":{"get":{"summary":"List runs for a workflow","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nLists one workflow's runs, newest first.\n\nUse `status` to keep only runs in one state, `since` to keep only runs after a moment in time, and `limit` with `offset` to page.\n\nManual runs started through [Run a workflow now](/api-reference/run-a-workflow-now) appear here with `is_test_run` set to `true`.\n\nThis endpoint is limited to 60 requests per minute.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"list_runs_api_apps__app_id__workflows__workflow_id__runs_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","title":"Workflow Id"},"description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","example":"68b1c0d4e7b91d003c45a1f2"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Most runs to return.","default":30,"title":"Limit"},"description":"Most runs to return.","example":30},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"How many runs to skip, for paging.","default":0,"title":"Offset"},"description":"How many runs to skip, for paging.","example":0},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Keep only runs in this state, for example `failed`.","title":"Status"},"description":"Keep only runs in this state, for example `failed`.","example":"failed"},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Keep only runs that started at or after this ISO 8601 datetime.","title":"Since"},"description":"Keep only runs that started at or after this ISO 8601 datetime.","example":"2026-08-01T00:00:00Z"}],"responses":{"200":{"description":"The workflow's runs, newest first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RunItem"},"title":"WorkflowRunHistory"}}}},"400":{"description":"`since` is not an ISO 8601 datetime."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"There is no workflow with this ID on this app."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/workflows/{workflow_id}/runs/{run_id}":{"get":{"summary":"Get workflow run","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReads one run with its step-by-step log.\n\n`steps` walks the tasks the run executed in order, each with its status, timing, and error if it failed. `definition_version_id` names the version the run executed, which can be older than the workflow's current one.\n\nEach step also carries the data that went into it and came out of it. Those fields are not documented here because their shape is whatever your workflow passes around, but they are in the response.\n\nThis endpoint is limited to 120 requests per minute.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"get_run_api_apps__app_id__workflows__workflow_id__runs__run_id__get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","title":"Workflow Id"},"description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","example":"68b1c0d4e7b91d003c45a1f2"},{"name":"run_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the run, as returned in `run_id` by [List runs for a workflow](/api-reference/list-runs-for-a-workflow).","title":"Run Id"},"description":"ID of the run, as returned in `run_id` by [List runs for a workflow](/api-reference/list-runs-for-a-workflow).","example":"0195f2a1-4c3e-7b21-9f0d-2a5c8e1b4d77"}],"responses":{"200":{"description":"The run and its steps.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunDetailResponse"}}}},"404":{"description":"There is no workflow with this ID on this app, or no run with this ID on that workflow."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key."}}}},"/api/apps/{app_id}/workflows/{workflow_id}/runs/{run_id}/analyze":{"post":{"summary":"Analyze a failed workflow run","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nExplains why a run failed, in plain language.\n\nThis only works on runs whose `status` is `failed`. Anything else returns a 400.\n\nThe first call runs a language model over the run's steps and its definition, then stores the result, so later calls for the same run return the stored text without paying for it again.\n\nCheck that `explanation` is non-empty before showing it. If the model is unavailable you still get a 200, with `explanation` and `generated_at` both empty rather than an error.\n\nThis endpoint is limited to 5 requests per minute.","operationId":"analyze_run_api_apps__app_id__workflows__workflow_id__runs__run_id__analyze_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","title":"Workflow Id"},"description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","example":"68b1c0d4e7b91d003c45a1f2"},{"name":"run_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the run, as returned in `run_id` by [List runs for a workflow](/api-reference/list-runs-for-a-workflow).","title":"Run Id"},"description":"ID of the run, as returned in `run_id` by [List runs for a workflow](/api-reference/list-runs-for-a-workflow).","example":"0195f2a1-4c3e-7b21-9f0d-2a5c8e1b4d77"}],"responses":{"200":{"description":"The explanation. Check that it is non-empty.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzeRunResponse"}}}},"400":{"description":"The run did not fail, so there is nothing to explain."},"404":{"description":"There is no workflow with this ID on this app, or no run with this ID on that workflow."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key."}}}},"/api/apps/{app_id}/workflows/{workflow_id}/runs/{run_id}/cancel":{"post":{"summary":"Cancel a workflow run","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nAsks a running workflow to stop.\n\n`status` comes back as `cancelling`, not `cancelled`: the request is acknowledged here and the run writes its final row once it stops. Poll [Get workflow run](/api-reference/get-workflow-run) to see it settle.\n\nCalling this on a run that has already finished is not an error. You get a 200 carrying that run's current status and nothing changes, so a cancel racing a run that just completed is safe to retry.\n\nThis endpoint is limited to 10 requests per minute.","operationId":"cancel_run_api_apps__app_id__workflows__workflow_id__runs__run_id__cancel_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workflows you want to work with.","title":"App Id"},"description":"ID of the app whose workflows you want to work with.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","title":"Workflow Id"},"description":"ID of the workflow, as returned in `id` by [List workflows](/api-reference/list-workflows).","example":"68b1c0d4e7b91d003c45a1f2"},{"name":"run_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the run, as returned in `run_id` by [List runs for a workflow](/api-reference/list-runs-for-a-workflow).","title":"Run Id"},"description":"ID of the run, as returned in `run_id` by [List runs for a workflow](/api-reference/list-runs-for-a-workflow).","example":"0195f2a1-4c3e-7b21-9f0d-2a5c8e1b4d77"}],"responses":{"200":{"description":"The cancel was accepted, or the run had already finished.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelRunResponse"}}}},"404":{"description":"There is no workflow with this ID on this app, or no run with this ID on that workflow."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, the app still runs the older automations engine instead of workflows, or you used a workspace API key."}}}},"/api/apps/{app_id}/payments/analytics":{"get":{"summary":"Get payment analytics","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns what an app earned: the totals for a window, and a day-by-day series you can chart.\n\nMoney is reported in the smallest unit of the currency, so `125000` is 1,250.00 where the currency has two decimal places. Read `net_revenue` for what the app actually kept, since `gross_revenue` counts refunded and disputed payments too.\n\nSet the window with `start_date` and `end_date`, which you must send together. With neither, `period` picks the last 7, 30 or 90 whole UTC days including today, and 30 is the default. Sending only one of the two dates falls back to `period` and silently ignores the date you sent, so send both or neither.\n\n<Warning>Read `summary.currency` before you read any amount. It is `null` when the app took money in more than one currency in the window, and the amounts are then sums across currencies, which is not a number you can show anyone. Use `available_currencies` to see which ones are present and pass `currencies` to narrow to one.</Warning>\n\n<Note>An unknown or malformed currency code in `currencies` is dropped rather than rejected, and if every code you send is unusable the response comes back as zeros rather than an error. Send lowercase three-letter codes taken from `available_currencies`.</Note>\n\nOnly live money is counted. Payments an app took while its payment provider was still in test mode are excluded.\n\n<Note>This reads an analytics store rather than the payment provider, so the last few minutes of activity can be missing, and a window with no transactions is reported as zeros rather than as an error.</Note>\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"get_payment_analytics_api_apps__app_id__payments_analytics_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose payments to read.","title":"App Id"},"description":"ID of the app whose payments to read.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"First moment of the window, as an ISO 8601 timestamp. Send it together with `end_date`, because sending only one falls back to `period` and ignores the one you sent.","title":"Start Date"},"description":"First moment of the window, as an ISO 8601 timestamp. Send it together with `end_date`, because sending only one falls back to `period` and ignores the one you sent.","example":"2026-08-01T00:00:00Z"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Last moment of the window, as an ISO 8601 timestamp. Send it together with `start_date`.","title":"End Date"},"description":"Last moment of the window, as an ISO 8601 timestamp. Send it together with `start_date`.","example":"2026-08-31T23:59:59Z"},{"name":"period","in":"query","required":false,"schema":{"enum":["7d","30d","90d"],"type":"string","description":"Window to use when you send no dates, counted back over whole UTC days including today. Either `7d`, `30d` or `90d`.","default":"30d","title":"Period"},"description":"Window to use when you send no dates, counted back over whole UTC days including today. Either `7d`, `30d` or `90d`.","example":"30d"},{"name":"currencies","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Narrow the totals to these currencies, as a comma-separated list of three-letter ISO 4217 codes. Case does not matter. For example, `usd,eur` reports only those two. Take the values from `available_currencies`.","title":"Currencies"},"description":"Narrow the totals to these currencies, as a comma-separated list of three-letter ISO 4217 codes. Case does not matter. For example, `usd,eur` reports only those two. Take the values from `available_currencies`.","example":"usd,eur"}],"responses":{"200":{"description":"The app's payment totals and daily series.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentAnalyticsResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or the app does not exist. A missing app and an app you cannot reach are deliberately the same answer."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/github/organizations":{"get":{"summary":"List GitHub organizations","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nLists the GitHub accounts you can create the app's repository in. Your personal account comes first, then your organizations alphabetically.\n\nOnly accounts with the Base44 GitHub App installed appear, so install it on the account you want before you call this. Your own GitHub account also has to be connected to Base44, which you do in the Base44 online app editor rather than through this API.\n\nWhen the app's workspace limits GitHub repositories to approved organizations, this returns only the approved ones, and otherwise it returns all of them. Call [Get GitHub organization policy](/api-reference/get-github-organization-policy) to see whether that limit is on.\n\nIf you're a workspace owner or admin, this list is filtered by the limit even though you aren't bound by it, so it can leave out an organization you're allowed to connect under.","operationId":"get_organizations_api_apps__app_id__github_organizations_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workspace decides which GitHub organizations you can use.","title":"App Id"},"description":"ID of the app whose workspace decides which GitHub organizations you can use.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The GitHub accounts you can create the app's repository in.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GitHubOrganization"},"title":"GitHubOrganizations"}}}},"400":{"description":"Your GitHub account isn't connected to Base44."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"Your workspace plan doesn't include the GitHub integration."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"App not found."}}}},"/api/apps/{app_id}/github/org-policy":{"get":{"summary":"Get GitHub organization policy","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nTells you whether the app's workspace limits GitHub repositories to an approved list of organizations.\n\nWhen `restricted` is `true`, you can create the app's repository only under one of the approved organizations. The approved list itself is managed in workspace settings.\n\nWorkspace owners and admins can connect under any organization, even when the policy restricts organizations for other users. However, the organization list they read is filtered by the restriction policy, so [List GitHub organizations](/api-reference/list-github-organizations) returns the same list for them as it does for other users.","operationId":"get_org_policy_api_apps__app_id__github_org_policy_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose workspace decides which GitHub organizations you can use.","title":"App Id"},"description":"ID of the app whose workspace decides which GitHub organizations you can use.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Whether the workspace limits which GitHub organizations you can use.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GithubOrgPolicyStatus"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"App not found."}}}},"/api/apps/{app_id}/github/connect":{"post":{"summary":"Connect a GitHub repository","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nCreates a new private GitHub repository and connects the app to it.\n\nOnly the app's owner can connect a repository, and the workspace plan has to include the GitHub integration. An app that already has a connection can't connect again, and this API can't disconnect one. You do that in the Base44 online app editor.\n\nThe call creates the repository, installs the webhook that tells Base44 about new commits, and pushes the app's current code as the first commit. The response comes back only after all of that finishes. The webhook is the one step that can fail without failing the connection, so check `webhook_active` in [Get GitHub connection](/api-reference/get-github-connection) afterwards.\n\nIf any of the rest fails, Base44 undoes the connection but leaves the repository it already created on GitHub. Delete that repository or send a different `repo_name` before you retry. Check [Get GitHub connection](/api-reference/get-github-connection) first, because a failure late in the call can leave the connection in place.\n\nFrom then on the repository is where the app's code lives. Base44 pushes each change to it, and you bring work done in GitHub back with [Pull changes from GitHub](/api-reference/pull-changes-from-github).","operationId":"connect_repository_api_apps__app_id__github_connect_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to connect to a GitHub repository.","title":"App Id"},"description":"ID of the app to connect to a GitHub repository.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectRepoRequest"}}}},"responses":{"200":{"description":"The repository that was created and connected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RepositoryConnectionResponse"}}}},"400":{"description":"The `repo_name` is invalid, the repository already exists, the app is already connected, your GitHub account isn't connected to Base44, the `installation_id` isn't one you can use on that account, or the GitHub App on this account covers selected repositories only."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"Your workspace plan doesn't include the GitHub integration."},"403":{"description":"You aren't the app's owner, the app's workspace doesn't approve this GitHub organization, or you used a workspace API key."},"404":{"description":"App not found."},"409":{"description":"The app is busy with another operation. Wait for it to finish and try again."},"422":{"description":"The request body is missing a required field, or one of its values has the wrong type."}}}},"/api/apps/{app_id}/github/connection":{"get":{"summary":"Get GitHub connection","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns the app's GitHub repository connection, if it has one.\n\nCalling this re-checks the connection against GitHub and tries to repair a broken one, so what it reports can change between two calls even when nobody has touched the app.\n\nAn app that never connected and one whose repository was disconnected look the same here, so this response can't tell you which happened.","operationId":"get_connection_status_api_apps__app_id__github_connection_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose GitHub connection to use.","title":"App Id"},"description":"ID of the app whose GitHub connection to use.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The app's GitHub connection.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionStatus"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"App not found."}}}},"/api/apps/{app_id}/github/branches/import":{"post":{"summary":"Import a GitHub branch","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nCreates a Base44 [branch](/developers/references/app-management/get-started/concepts#branches) from a branch that already exists in the connected GitHub repository.\n\nThe two are one line of work. Base44 commits to the GitHub branch you name and never creates a separate branch in the repository.\n\nName a GitHub branch that already exists and isn't the repository's default branch, which the app's main chat already uses. The name also has to be free among the app's active and merged Base44 branches, so you can import a given GitHub branch once.\n\nIf Base44 can't read the branch from GitHub, nothing is created and you can retry.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"import_branch_api_apps__app_id__github_branches_import_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to import the branch into.","title":"App Id"},"description":"ID of the app to import the branch into.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportBranchRequest"}}}},"responses":{"200":{"description":"The imported branch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportedBranchSummary"}}}},"400":{"description":"The `branch_name` is the repository's default branch, doesn't exist in the repository, or isn't a name Base44 can build on."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"App not found, or the app has no active GitHub connection."},"409":{"description":"The app is generating, a branch with this name already exists, or the app needs one edit on its main chat before it can use branches."},"422":{"description":"The request body is missing `branch_name`, or its value is empty or longer than 255 characters."},"502":{"description":"Base44 couldn't read the branch's files from GitHub. Nothing was created, so you can retry the request."}}}},"/api/apps/{app_id}/github/sync":{"post":{"summary":"Pull changes from GitHub","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nPulls new commits from the connected GitHub repository into the app.\n\nCall this after someone pushes to the repository, or before you read the app's code, so Base44 is working from the latest version. It applies only what is new since the last pull, and does nothing when the app is already up to date.\n\nA failed pull is reported in the response body rather than as an error, so read `synced` and `error`. Base44 doesn't record the commits it couldn't apply, so the next pull picks them up again rather than skipping past them. A pull that fails late can already have added a chat message or a checkpoint, and retrying repeats those.\n\nA merge conflict means the repository's commits and the app's own changes touch the same lines. Hand it to [Resolve GitHub sync conflicts](/api-reference/resolve-github-sync-conflicts).\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"sync_github_to_base44_api_apps__app_id__github_sync_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose GitHub sync this affects.","title":"App Id"},"description":"ID of the app whose GitHub sync this affects.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The outcome of the pull.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GitHubPullResult"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded. The base limit is 20 requests per minute, and this endpoint shares it with [Resolve GitHub sync conflicts](/api-reference/resolve-github-sync-conflicts). See [Rate limits](/developers/references/app-management/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/github/sync/resolve-conflicts":{"post":{"summary":"Resolve GitHub sync conflicts","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nAsks the builder to resolve the merge conflicts left by a pull from GitHub.\n\nCall this after [Pull changes from GitHub](/api-reference/pull-changes-from-github) reports a merge conflict. Base44 runs a builder [turn](/developers/references/app-management/get-started/concepts#turns) over the conflicted files, and one more automatic attempt if publishing the resolution finds that GitHub has moved on. The turn consumes credits like any other builder work. The response comes back only when the run ends, which can take minutes.\n\nResolution always lands on the repository's default branch.","operationId":"resolve_github_conflicts_api_apps__app_id__github_sync_resolve_conflicts_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose GitHub sync this affects.","title":"App Id"},"description":"ID of the app whose GitHub sync this affects.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"How the resolution run ended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolveConflictsResponse"}}}},"400":{"description":"The app's workspace is out of credits, so the resolution turn can't run."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"App not found, or the app has no active GitHub connection."},"409":{"description":"The builder is busy with another turn. Wait for it to finish and try again."},"429":{"description":"Rate limit exceeded. The base limit is 20 requests per minute, and this endpoint shares it with [Pull changes from GitHub](/api-reference/pull-changes-from-github). See [Rate limits](/developers/references/app-management/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/github/sync/manual":{"post":{"summary":"Push changes to GitHub","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nPushes the app's latest saved version to the connected repository.\n\nBase44 pushes each saved version by itself while automatic sync is on. Use this when a push failed, or when you've turned automatic sync off, since it pushes regardless of that setting.\n\nThe push commits the app's latest saved version on the main line, so it publishes saved work rather than the current chat [turn](/developers/references/app-management/get-started/concepts#turns), and it never pushes a Base44 branch.\n\nA failed push is reported in the response body rather than as an error, so read `success` and `error`.\n\n<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>","operationId":"trigger_manual_sync_api_apps__app_id__github_sync_manual_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose GitHub sync this affects.","title":"App Id"},"description":"ID of the app whose GitHub sync this affects.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The outcome of the push.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GitHubPushResult"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. These endpoints take a personal API key."},"404":{"description":"App not found."}}}}},"components":{"schemas":{"AICampaignBrief":{"properties":{"business_name":{"type":"string","title":"Business Name","description":"Name of the business to build campaigns for.","default":"","example":"Nordwind Furniture"},"business_description":{"type":"string","title":"Business Description","description":"What the business sells, in a sentence or two. This is what the suggestions are built from.","default":"","example":"Handmade solid oak dining tables, built to order in San Francisco."},"landing_page_url":{"type":"string","title":"Landing Page Url","description":"Page the campaigns would send people to.","default":"","example":"https://nordwind-furniture.com"},"campaign_goal":{"type":"string","title":"Campaign Goal","description":"What the campaigns are for: `traffic`, `leads` or `sales`.","default":"traffic","example":"traffic"},"daily_budget_micros":{"type":"integer","title":"Daily Budget Micros","description":"Daily budget each suggestion is sized for, in micros (1,000,000 micros = 1 unit of the account's currency).","default":30000000,"example":30000000},"suggestion_count":{"type":"integer","maximum":5.0,"minimum":1.0,"title":"Suggestion Count","description":"How many suggestions to return. Only three approaches exist, so 4 and 5 return the same three as 3.","default":3,"example":3}},"type":"object","title":"AICampaignBrief"},"AcceptAssetResult":{"properties":{"accepted":{"type":"boolean","title":"Accepted","description":"Always `true` on a success, so it carries no information beyond the status code. Read `asset_group_count` to see how far the attach reached.","example":true},"session_asset_id":{"type":"string","title":"Session Asset Id","description":"The asset that was attached, echoed from the path.","example":"b7f3a1c8-52d4-4a0e-9b31-2c6f0d8e4a19"},"asset_field_type":{"type":"string","title":"Asset Field Type","description":"Which slot on the ad this asset fills. Text assets are `HEADLINE`, `LONG_HEADLINE` or `DESCRIPTION`. Image assets are `MARKETING_IMAGE`, `SQUARE_MARKETING_IMAGE`, `PORTRAIT_MARKETING_IMAGE` or `TALL_PORTRAIT_MARKETING_IMAGE`.","example":"SQUARE_MARKETING_IMAGE"},"kind":{"type":"string","title":"Kind","description":"Whether the asset is copy (`text`) or a picture (`image`).","example":"image"},"source":{"type":"string","title":"Source","description":"Which engine wrote it. The value is `google` for Google's own asset generation and `inhouse` for the Base44 model that covers languages Google does not generate for, and that stands in when Google's call fails.","example":"google"},"asset_group_count":{"type":"integer","title":"Asset Group Count","description":"How many of the campaign's asset groups the asset was linked to. The asset goes on every asset group the campaign has, so this is that count.","example":2},"resource_name":{"type":"string","title":"Resource Name","description":"Google Ads resource name of the asset that now holds this creative. Empty when Google's response carried no resource name, which does not mean the attach failed.","example":"customers/1234567890/assets/98765432"}},"type":"object","required":["accepted","session_asset_id","asset_field_type","kind","source","asset_group_count","resource_name"],"title":"AcceptAssetResult","description":"The outcome of attaching a generated asset to a campaign."},"AcceptTermsResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Outcome of the request. A status of `accepted` means the acceptance was recorded, while `not_recorded` means it was not written because the account is in a blocked or terminal state, which is a failure to act on rather than a no-op.","example":"accepted"}},"type":"object","required":["status"],"title":"AcceptTermsResponse","description":"Outcome of accepting the Google Ads terms."},"AccountInsight":{"properties":{"id":{"type":"string","title":"Id","description":"Stable ID for the suggestion. Pass it as `insight_id` to [Dismiss insight](/api-reference/dismiss-google-ads-insight) to stop it coming back.","example":"increase_budget:21458812345"},"severity":{"type":"string","title":"Severity","description":"How urgent the suggestion is: `high`, `medium`, or `low`.","example":"medium"},"title":{"type":"string","title":"Title","description":"One-line summary of the suggestion.","example":"Increase budget for Spring sale - Search"},"description":{"type":"string","title":"Description","description":"The suggestion in full, written for an app owner to read as-is.","example":"This campaign is consistently spending its full daily budget. Raising it could capture more clicks and conversions."},"action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action","description":"What resolving the suggestion involves: `edit_budget`, `edit_sitelinks`, `improve_assets`, or `open_campaign`. `null` when Base44 could not resolve a campaign to act on.","example":"edit_budget"},"action_payload":{"additionalProperties":true,"type":"object","title":"Action Payload","description":"The values the suggestion proposes. `campaign_ids` names the campaigns it applies to, `field` and `value` carry a proposed budget in micros where there is one.","example":{"campaign_ids":["68b1c0d4e7b91d003c45a1f2"],"field":"budget","value":30000000}},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"`google` when the suggestion is Google Ads' own recommendation rather than Base44's. Absent on Base44's own suggestions.","example":"google"},"recommendation_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recommendation Type","description":"Present with `source`. `CAMPAIGN_BUDGET` for a Google budget recommendation, `AD_STRENGTH_DROP` for an ad-strength one.","example":"CAMPAIGN_BUDGET"}},"type":"object","required":["id","severity","title","description"],"title":"AccountInsight","description":"One suggestion in the account's insight feed."},"AggregationGroup":{"properties":{"group":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Group","description":"Value of the grouped field, or `null` when the events in this group have no value for it.","example":"checkout_completed"},"metrics":{"additionalProperties":{"anyOf":[{"type":"number"},{"type":"integer"}]},"type":"object","title":"Metrics","description":"The single metric this entry reports, keyed by the `name` you gave it.","example":{"event_count":842}}},"type":"object","required":["group"],"title":"AggregationGroup","description":"A single group with its field value and computed metrics."},"AggregationMetric":{"properties":{"name":{"type":"string","maxLength":64,"pattern":"^[a-zA-Z_][a-zA-Z0-9_]*$","title":"Name","description":"Name to report this metric under. It becomes a key in `metrics` and `totals` in the response.","example":"unique_users"},"function":{"type":"string","enum":["count","count_unique","sum","avg","min","max","p50","p90","p95","p99"],"title":"Function","description":"How to aggregate the field. A `count` counts events, `count_unique` counts distinct values, and the rest compute over the field's numeric values.","example":"count_unique"},"field":{"anyOf":[{"type":"string","maxLength":128,"pattern":"^(properties\\.)?[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)*$"},{"type":"null"}],"title":"Field","description":"Field to aggregate. Required for every function except `count`. Top-level fields are `user_id`, `session_id`, `event_id`, `event_name`, and `page_url`. Prefix a key with `metadata.` for the device information Base44 captures itself: `metadata.device_type`, `metadata.os`, and `metadata.country`. Any other name reads as an event property, so `amount` and `properties.amount` mean the same thing.","example":"user_id"}},"type":"object","required":["name","function"],"title":"AggregationMetric","description":"A single aggregation metric to compute."},"AnalyticsEvent":{"properties":{"event_id":{"type":"string","title":"Event Id","description":"ID of the event.","example":"4f1c9a02-8b7d-4e6a-9c31-5d2e7f8a0b41"},"event_name":{"type":"string","title":"Event Name","description":"Name of the event.","example":"checkout_completed"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp","description":"Time the event occurred, as reported by the app, as a UTC timestamp in ISO 8601 format.","example":"2026-08-02T14:30:00"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id","description":"ID of the app user who triggered the event, or `null` if it was not attributed to one.","example":"6891ab34d2f07e5c1b9a2d48"},"session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Id","description":"Session the event belongs to, or `null` if the app sent none.","example":"9a7c2f10-3b4d-4e58-8c61-0d2f5a7b9e13"},"page_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Page Url","description":"URL of the page the event happened on, or `null` if the app sent none.","example":"https://my-crm.base44.app/checkout"},"properties":{"additionalProperties":true,"type":"object","title":"Properties","description":"The custom properties the app sent with this event, or `{}` if it sent none. Keys and value types are whatever the app tracks.","example":{"amount":49.9,"plan":"pro"}},"metadata":{"$ref":"#/components/schemas/AnalyticsEventMetadata","description":"Device information Base44 captured when it received the event."}},"type":"object","required":["event_id","event_name","timestamp","properties","metadata"],"title":"AnalyticsEvent"},"AnalyticsEventMetadata":{"properties":{"device_type":{"anyOf":[{"type":"string","enum":["desktop","mobile","tablet"]},{"type":"null"}],"title":"Device Type","description":"Device the event came from, derived from the request's user agent, or `null` when the app sent no user agent.","example":"mobile"},"os":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Os","description":"Operating system the event came from, or `null` when it could not be derived.","example":"iOS"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country","description":"Two-letter country code the event came from. The value is `null` when the request reached Base44 without a country header, `XX` when the country is unknown, and `T1` for traffic over Tor.","example":"US"}},"type":"object","title":"AnalyticsEventMetadata"},"AnalyticsEventPage":{"properties":{"total":{"type":"integer","title":"Total","description":"Number of events matching the query, across all pages.","example":1842},"events":{"items":{"$ref":"#/components/schemas/AnalyticsEvent"},"type":"array","title":"Events","description":"The requested page of events, newest first.","example":[{"event_id":"4f1c9a02-8b7d-4e6a-9c31-5d2e7f8a0b41","event_name":"checkout_completed","metadata":{"country":"US","device_type":"mobile","os":"iOS"},"page_url":"https://my-crm.base44.app/checkout","properties":{"plan":"pro","amount":49.9},"session_id":"9a7c2f10-3b4d-4e58-8c61-0d2f5a7b9e13","timestamp":"2026-08-02T14:30:00","user_id":"6891ab34d2f07e5c1b9a2d48"}]},"has_more":{"type":"boolean","title":"Has More","description":"Whether more events match beyond this page.","example":true},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"Offset to pass as `offset` to get the next page, or `null` when `has_more` is `false`.","example":100}},"type":"object","required":["total","events","has_more"],"title":"AnalyticsEventPage","description":"One page of analytics events matching a query."},"AnalyzeRunResponse":{"properties":{"explanation":{"type":"string","title":"Explanation","description":"Why the run failed, in plain language. Empty when the model could not be reached.","example":"The email step failed because the recipient address was missing from the trigger payload."},"generated_at":{"type":"string","title":"Generated At","description":"When the explanation was produced, as an ISO 8601 timestamp. Empty when none was produced.","example":"2026-08-25T09:20:11Z"}},"type":"object","required":["explanation","generated_at"],"title":"AnalyzeRunResponse","description":"AI-generated failure analysis."},"AppStatusResponse":{"properties":{"state":{"type":"string","enum":["ready","processing","error"],"title":"State","description":"Where the app is in its build lifecycle. Ready means idle with no build in progress, processing means the app is being generated or modified, and error means the last build failed. This tracks building, not publishing.","example":"ready"},"details":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Details","description":"Human readable note about the current state, such as what is being processed or why it failed, or `null` when there is nothing to report.","example":"Publishing app"},"request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Id","description":"ID of the request that last changed the status, or `null` if the status has never changed. Useful when reporting an issue.","example":"a1b2c3d4e5f67890abcdef12"},"last_updated_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Updated Date","description":"Time the status was last updated, as a UTC timestamp in ISO 8601 format.","example":"2026-08-02T14:30:00Z"},"error_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Source","description":"Where the failure originated when `state` is `error`, or `null` otherwise. A value of `paywall` means the work was blocked because the app's workspace has no credits left.","example":"build"},"paywall_context":{"anyOf":[{"$ref":"#/components/schemas/PaywallStatusContextResponse"},{"type":"null"}],"description":"Present when the operation was blocked by a plan limit, describing what was evaluated, or `null` otherwise."}},"type":"object","required":["state"],"title":"AppStatusResponse","description":"The app's current build status."},"AppSummary":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Display name of the app.","example":"My CRM"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug","description":"URL slug for the app, auto generated from the name and app ID or set to a custom value, or `null` if the app has no slug yet. The published URL is built from it.","example":"my-crm-3c45a1f2"},"user_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Description","description":"Description of the app, or `null` if none was set. On a newly created app this holds the original prompt text.","example":"A CRM to track leads and deals"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By","description":"Email of the user who created the app.","example":"developer@example.com"},"created_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created Date","description":"Time the app was created, as a UTC timestamp in ISO 8601 format.","example":"2026-08-01T09:15:00"},"updated_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated Date","description":"Time the app document was last written, as a UTC timestamp in ISO 8601 format.","example":"2026-08-02T14:30:00"},"status":{"anyOf":[{"$ref":"#/components/schemas/AppStatusResponse"},{"type":"null"}],"description":"The app's current build status. Poll while a build is in progress to watch it finish. This tracks building, not publishing."},"last_deployed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Deployed At","description":"Time the app was last published, as a UTC timestamp in ISO 8601 format, or `null` if it has never been published.","example":"2026-08-02T14:30:00"},"screenshot_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Screenshot Url","description":"URL of a screenshot of the published app. Captured shortly after each publish, so it can briefly lag or be `null` right after publishing.","example":"https://storage.base44.com/screenshots/6820f3a4e7b91d003c45a1f2.png"},"preview_screenshot_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preview Screenshot Url","description":"URL of a preview screenshot taken before publishing, distinct from `screenshot_url`, or `null` if none has been captured.","example":"https://storage.base44.com/previews/6820f3a4e7b91d003c45a1f2.png"}},"type":"object","title":"AppSummary","description":"An app in a workspace, limited to the properties the caller requested."},"AppUserCountResponse":{"properties":{"count":{"type":"integer","title":"Count","description":"Number of the app's users you can read.","example":42}},"type":"object","required":["count"],"title":"AppUserCountResponse","description":"The number of users registered in an app."},"ArchiveResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Always `archived`.","example":"archived"},"workflow_id":{"type":"string","title":"Workflow Id","description":"ID of the workflow that was archived.","example":"68b1c0d4e7b91d003c45a1f2"}},"type":"object","required":["status","workflow_id"],"title":"ArchiveResponse"},"AssetGroupCombinationRow":{"properties":{"asset_group":{"type":"string","title":"Asset Group","description":"Name of the asset group the combination belongs to.","example":"Spring sale - Asset Group"},"asset_group_id":{"type":"string","title":"Asset Group Id","description":"ID of that asset group, matching `asset_group_id` on [List asset performance](/api-reference/list-google-ads-asset-performance).","example":"9988776655"},"campaign":{"type":"string","title":"Campaign","description":"Name of the campaign the asset group belongs to.","example":"Spring sale - Performance Max"},"assets":{"items":{"type":"string"},"type":"array","title":"Assets","description":"The assets in the combination, each as its text or its Google Ads name. An asset Base44 could not label falls back to its resource name.","example":["Handmade oak furniture","Free delivery across Germany","spring-hero-1200x628"]}},"type":"object","required":["asset_group","asset_group_id","campaign","assets"],"title":"AssetGroupCombinationRow","description":"One combination of assets that served together."},"AssetPerformanceRow":{"properties":{"resource_name":{"type":"string","title":"Resource Name","description":"Google Ads resource name for the asset's link to its asset group or campaign. Empty on rows Base44 synthesized from the campaign's saved creative.","example":"customers/1234567890/assetGroupAssets/2145881234~9988776655~HEADLINE"},"field_type":{"type":"string","title":"Field Type","description":"The role the asset plays, for example `HEADLINE`, `LONG_HEADLINE`, `DESCRIPTION`, `MARKETING_IMAGE`, `LOGO`, or `YOUTUBE_VIDEO`.","example":"HEADLINE"},"text":{"type":"string","title":"Text","description":"The asset's text. Empty for images, logos and videos.","example":"Handmade oak furniture"},"asset_name":{"type":"string","title":"Asset Name","description":"Google Ads' own name for the asset. Usually the only label an image, logo or video has.","example":"spring-hero-1200x628"},"type":{"type":"string","title":"Type","description":"Google Ads asset type, for example `TEXT`, `IMAGE`, or `YOUTUBE_VIDEO`.","example":"TEXT"},"image_url":{"type":"string","title":"Image Url","description":"Hosted URL for an image or logo asset. Empty for text and video assets.","example":"https://tpc.googlesyndication.com/simgad/1234567890"},"video_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Id","description":"YouTube video ID for a `YOUTUBE_VIDEO` asset. Empty on other assets in an asset group, and absent entirely on logo rows and on rows Base44 synthesized from launch creative.","example":"dQw4w9WgXcQ"},"campaign":{"type":"string","title":"Campaign","description":"Name of the campaign the asset serves in.","example":"Spring sale - Performance Max"},"asset_group_id":{"type":"string","title":"Asset Group Id","description":"ID of the asset group the asset belongs to. Empty for logos, which attach to the campaign instead.","example":"9988776655"},"asset_group_name":{"type":"string","title":"Asset Group Name","description":"Name of that asset group. Empty for the same reason.","example":"Spring sale - Asset Group"},"ad_strength":{"type":"string","title":"Ad Strength","description":"Google Ads' strength rating for the asset group: `POOR`, `AVERAGE`, `GOOD`, `EXCELLENT`, or `PENDING`. Empty for logos.","example":"GOOD"},"primary_status":{"type":"string","title":"Primary Status","description":"Whether the asset can serve, for example `ELIGIBLE`, `LIMITED`, `DISAPPROVED`, or `PENDING_REVIEW`. `GATHERING_DATA` is Base44's own value, used on a row synthesized from launch creative that Google has not echoed back yet.","example":"ELIGIBLE"},"primary_status_reasons":{"items":{"type":"string"},"type":"array","title":"Primary Status Reasons","description":"Why the asset is limited or disapproved. Empty when it serves normally.","example":["TRADEMARKS_IN_AD_TEXT"]},"policy_topics":{"items":{"type":"string"},"type":"array","title":"Policy Topics","description":"The Google Ads policy topics limiting the asset, without the generic status reasons `primary_status_reasons` also carries. Always empty for logos, which expose no policy detail, and absent on rows Base44 synthesized from launch creative.","example":["TRADEMARKS_IN_AD_TEXT"]},"performance_label":{"type":"string","title":"Performance Label","description":"Base44's own band for the asset, since Google publishes no per-asset rating for Performance Max: `LEARNING` while the campaign is still in its launch window, then `LOW`, `GOOD` or `EXCELLENT` from the asset's click-through rate against a threshold for its field type. `PENDING` and `DISAPPROVED` pass the serving status through instead. Empty when the asset cannot be rated, which is permanent for logos and for rows Base44 synthesized from launch creative.","example":"LEARNING"},"impressions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Impressions","description":"Impressions over the trailing 30 days. `null` when Google reported no row for the asset in that window, and absent entirely on logos and on rows Base44 synthesized from saved creative.","example":18400},"clicks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Clicks","description":"Clicks over the same 30 days, with the same `null` and absent cases.","example":612},"cost_micros":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cost Micros","description":"Spend over the same 30 days in micros, with the same `null` and absent cases.","example":412500000}},"type":"object","required":["resource_name","field_type","text","asset_name","type","image_url","campaign","asset_group_id","asset_group_name","ad_strength","primary_status","performance_label"],"title":"AssetPerformanceRow","description":"One creative asset in a Performance Max campaign."},"AutoRenewalResponse":{"properties":{"auto_renew":{"type":"boolean","title":"Auto Renew","description":"Whether auto-renewal is now on. The value is `true` after turning renewal on and `false` after turning it off.","example":true}},"type":"object","required":["auto_renew"],"title":"AutoRenewalResponse","description":"The account's auto-renewal setting after the change."},"BatchToolCallResponse":{"properties":{"results":{"anyOf":[{"items":{"$ref":"#/components/schemas/BatchToolCallResultSummary"},"type":"array"},{"type":"null"}],"title":"Results","description":"One entry per tool call that was answered, in the order they were processed. A repeated ID appears once, so this can be shorter than the list you sent. Empty when the request was a duplicate that had already been applied."},"app":{"anyOf":[{"$ref":"#/components/schemas/ChatTurnResponse"},{"type":"null"}],"description":"The app once the resumed turn finished."}},"type":"object","title":"BatchToolCallResponse","description":"The app after a batch of tool calls was answered, with one result per call."},"BatchToolCallResultSummary":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"ID of the tool call this result is for.","example":"toolu_01A9FJd3kP2mNqRs7VwXyZ4b"},"success":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Success","description":"Whether this tool call was answered successfully.","example":true},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why this tool call could not be answered, or `null` when it succeeded.","example":"Failed to process tool call"}},"type":"object","title":"BatchToolCallResultSummary"},"BillingCadenceResponse":{"properties":{"billing_cadence":{"type":"string","title":"Billing Cadence","description":"The cadence now in effect: `1w`, `2w`, or `1m`.","example":"1w"}},"type":"object","required":["billing_cadence"],"title":"BillingCadenceResponse","description":"The account's billing cadence after the change."},"BillingStatusResponse":{"properties":{"has_payment_method":{"type":"boolean","title":"Has Payment Method","description":"Whether a usable card is on file. Read it together with `stripe_unavailable`, which changes what a `false` means.","example":true},"stripe_unavailable":{"type":"boolean","title":"Stripe Unavailable","description":"Whether Base44 could not reach the payment provider. When it is `true`, `has_payment_method` reports `false` because the check fails closed, not because the card is missing. Retry rather than telling someone to add a card.","example":false},"currency_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency Code","description":"Currency the workspace is billed in, as an ISO 4217 code, or `null` before a card is on file.","example":"EUR"},"payment_method_brand":{"type":"string","title":"Payment Method Brand","description":"Card brand, for display. Empty when no card is on file.","example":"visa"},"payment_method_last4":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payment Method Last4","description":"Last four digits of the card, for display, or `null` when no card is on file.","example":"4242"}},"type":"object","required":["has_payment_method","stripe_unavailable","payment_method_brand"],"title":"BillingStatusResponse","description":"Whether the workspace can be charged for ad spend."},"BudgetEstimateRequest":{"properties":{"keyword_themes":{"items":{"type":"string"},"type":"array","title":"Keyword Themes","description":"Themes the campaign would match on. An empty list forecasts nothing useful.","example":["handmade oak furniture","custom dining tables"]},"daily_budget":{"type":"integer","title":"Daily Budget","description":"Daily budget to forecast against, in micros (1,000,000 micros = 1 unit of the account's currency).","default":0,"example":30000000}},"type":"object","title":"BudgetEstimateRequest"},"BudgetLimitResponse":{"properties":{"account_id":{"type":"string","title":"Account Id","description":"Account the cap applies to.","example":"68b1c0d4e7b91d003c45a1f8"},"monthly_limit_dollars":{"type":"integer","title":"Monthly Limit Dollars","description":"The cap now in effect, in whole units of the account currency rather than micros.","example":500}},"type":"object","required":["account_id","monthly_limit_dollars"],"title":"BudgetLimitResponse","description":"The account's monthly spend cap after the change."},"BulkBudgetFailure":{"properties":{"campaign_id":{"type":"string","title":"Campaign Id","description":"Campaign whose update failed.","example":"68b1c0d4e7b91d003c45a1f2"},"reason":{"type":"string","title":"Reason","description":"Why it failed. Free text for people to read, not a stable code to branch on. Some reasons mean the budget did change on Google and only Base44's record of it failed, so verify before treating the entry as unchanged.","example":"Budget below the minimum for this currency"}},"type":"object","required":["campaign_id","reason"],"title":"BulkBudgetFailure","description":"One campaign whose budget update did not apply."},"BulkBudgetUpdateResponse":{"properties":{"succeeded":{"items":{"type":"string"},"type":"array","title":"Succeeded","description":"IDs of the campaigns whose budget was updated.","example":["68b1c0d4e7b91d003c45a1f2"]},"failed":{"items":{"$ref":"#/components/schemas/BulkBudgetFailure"},"type":"array","title":"Failed","description":"Campaigns that were left unchanged, each with a reason. Empty when every update applied.","example":[{"campaign_id":"68b1c0d4e7b91d003c45a1f4","reason":"Budget below the minimum for this currency"}]}},"type":"object","required":["succeeded","failed"],"title":"BulkBudgetUpdateResponse","description":"Per-campaign outcome of a bulk budget update."},"BusinessProfileLinkResponse":{"properties":{"business_profile_location_id":{"type":"string","title":"Business Profile Location Id","description":"The linked location as `locations/<id>`. Empty after unlinking.","example":"locations/12345"}},"type":"object","required":["business_profile_location_id"],"title":"BusinessProfileLinkResponse","description":"The account's linked Google Business Profile location."},"CampaignBriefResource":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the brief. Pass this as `brief_id` to read, update, or delete it.","example":"68b1c0d4e7b91d003c45a1f5"},"platform_type":{"type":"string","title":"Platform Type","description":"Which campaign type the brief is for, `SMART` or `PERFORMANCE_MAX`.","example":"SMART"},"source":{"type":"string","title":"Source","description":"Who wrote the brief. The value is `ai` when Base44 generated it and `user` when you created it.","example":"user"},"business_name":{"type":"string","title":"Business Name","description":"Business name to advertise.","example":"Nordwind Furniture"},"business_description":{"type":"string","title":"Business Description","description":"What the business does. Base44 uses this when it generates copy.","example":"Handmade oak furniture, delivered across Germany."},"landing_page_url":{"type":"string","title":"Landing Page Url","description":"URL the ads will send clicks to.","example":"https://example.com/spring"},"language":{"type":"string","title":"Language","description":"Language the ad copy is written in, as a lowercase two-letter code. See the note on this field in each endpoint. Create and update echo what you send, while list and get report the normalized value.","example":"en"},"keywords":{"items":{"type":"string"},"type":"array","title":"Keywords","description":"Keywords the campaign should match.","example":["oak furniture","handmade table"]},"target_audience":{"type":"string","title":"Target Audience","description":"Free-text description of who the campaign is for. Empty when unset.","example":"Homeowners aged 30 to 55"},"daily_budget_micros":{"type":"integer","title":"Daily Budget Micros","description":"Planned daily budget in micros of the account currency, so `15000000` is 15.00. The value is `0` when unset.","example":15000000},"geo_targets":{"items":{"type":"string"},"type":"array","title":"Geo Targets","description":"Google Ads geo target constant IDs to target.","example":["1003854"]},"headlines":{"items":{"type":"string"},"type":"array","title":"Headlines","description":"Ad headlines. Populated by generate, or by you.","example":["Handmade oak furniture"]},"descriptions":{"items":{"type":"string"},"type":"array","title":"Descriptions","description":"Ad description lines.","example":["Built to last. Delivered free."]},"schedule_type":{"type":"string","title":"Schedule Type","description":"When the campaign runs. Use `always` to run continuously, or `custom` to use `schedule_days`.","example":"always"},"schedule_days":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Schedule Days","description":"Day and hour windows to run in, used only when `schedule_type` is `custom`.","example":[{"day":"MONDAY","end_hour":17,"start_hour":9}]},"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"When the brief was created.","example":"2026-08-25T11:20:00Z"},"updated_date":{"type":"string","format":"date-time","title":"Updated Date","description":"When the brief was last changed.","example":"2026-08-25T14:05:00Z"}},"type":"object","required":["id","platform_type","source","business_name","business_description","landing_page_url","language","keywords","target_audience","daily_budget_micros","geo_targets","headlines","descriptions","schedule_type","schedule_days","created_date","updated_date"],"title":"CampaignBriefResource","description":"A saved campaign brief."},"CampaignChangeLogEntry":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the log entry.","example":"68b1c0d4e7b91d003c45a1f3"},"campaign_id":{"type":"string","title":"Campaign Id","description":"Campaign the change applies to.","example":"68b1c0d4e7b91d003c45a1f2"},"event_type":{"type":"string","title":"Event Type","description":"What happened: `CREATED`, `LAUNCHED`, `PAUSED`, `RESUMED`, `ENDED`, `DELETED`, or `EDITED`.","example":"PAUSED"},"details":{"additionalProperties":true,"type":"object","title":"Details","description":"Extra detail about the change. For an `EDITED` entry this carries `field`, `before`, and `after`. It is empty for the other event types.","example":{"after":15000000,"before":10000000,"field":"budget"}},"actor":{"type":"string","title":"Actor","description":"Who made the change: `system` for an automatic change such as a billing pause, otherwise the ID of the user who made it.","example":"system"},"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"When the change happened.","example":"2026-08-25T14:05:00Z"}},"type":"object","required":["id","campaign_id","event_type","details","actor","created_date"],"title":"CampaignChangeLogEntry","description":"One entry in a campaign's change log."},"CampaignListItem":{"properties":{"id":{"type":"string","title":"Id","description":"Base44's ID for the campaign. Pass this as `campaign_id` on the other campaign endpoints.","example":"68b1c0d4e7b91d003c45a1f2"},"google_campaign_id":{"type":"string","title":"Google Campaign Id","description":"The campaign's ID in Google Ads.","example":"21098765432"},"campaign_name":{"type":"string","title":"Campaign Name","description":"Name shown for the campaign.","example":"Spring sale in Berlin"},"campaign_type":{"type":"string","title":"Campaign Type","description":"Campaign type.","example":"SMART"},"status":{"type":"string","title":"Status","description":"Serving state. A status of `REMOVED` is a deleted campaign, which stays listed for reporting.","example":"ENABLED"},"daily_budget_micros":{"type":"integer","title":"Daily Budget Micros","description":"Daily budget in micros of the account currency, so `15000000` is 15.00.","example":15000000},"start_date":{"type":"string","title":"Start Date","description":"Date the campaign started, or empty when it has not.","example":"2026-08-25"},"impressions":{"type":"integer","title":"Impressions","description":"Impressions, or `0` when `metrics_pending` is true.","example":15420},"clicks":{"type":"integer","title":"Clicks","description":"Clicks, or `0` when `metrics_pending` is true.","example":612},"cost_micros":{"type":"integer","title":"Cost Micros","description":"Spend in micros of the account currency, or `0` when `metrics_pending` is true.","example":248000000},"spend":{"type":"number","title":"Spend","description":"Spend as a decimal amount, taken from `cost_micros`, for display.","example":248.0},"conversions":{"type":"number","title":"Conversions","description":"Conversions, or `0` when `metrics_pending` is true.","example":31.0},"ctr":{"type":"number","title":"Ctr","description":"Click-through rate, or `0` when `metrics_pending` is true.","example":0.0397},"review_status":{"type":"string","title":"Review Status","description":"Google's policy review status. Always empty on a deleted campaign.","example":"REVIEWED"},"serving_status":{"type":"string","title":"Serving Status","description":"Google's serving status. Always empty on a deleted campaign.","example":"SERVING"},"primary_status":{"type":"string","title":"Primary Status","description":"Always empty on this endpoint. Read one campaign to get it.","example":""},"primary_status_reasons":{"items":{"type":"string"},"type":"array","title":"Primary Status Reasons","description":"Google's reasons why the campaign is limited or not serving. Always empty on a deleted campaign.","example":["CAMPAIGN_BUDGET_LIMITED"]},"metrics_pending":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Metrics Pending","description":"Present and `true` only when you passed `include_metrics=false`, meaning the metric fields above are zeroed rather than measured.","example":true}},"type":"object","required":["id","google_campaign_id","campaign_name","campaign_type","status","daily_budget_micros","start_date","impressions","clicks","cost_micros","spend","conversions","ctr","review_status","serving_status","primary_status","primary_status_reasons"],"title":"CampaignListItem","description":"One row of the campaign list, carrying metrics but not the landing page, targeting, or phone number. Read one campaign for those."},"CampaignPatchResult":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Base44's ID for the campaign. Absent when you changed `images`, which reports `updated_field_types` instead.","example":"68b1c0d4e7b91d003c45a1f2"},"campaign_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign Name","description":"Present when you changed `name`.","example":"Spring sale in Berlin"},"daily_budget_micros":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Daily Budget Micros","description":"Present when you changed `budget`.","example":15000000}},"type":"object","title":"CampaignPatchResult","description":"What an update returns, the campaign ID plus only the section that changed."},"CampaignResource":{"properties":{"id":{"type":"string","title":"Id","description":"Base44's ID for the campaign. Pass this as `campaign_id` on the other campaign endpoints.","example":"68b1c0d4e7b91d003c45a1f2"},"google_campaign_id":{"type":"string","title":"Google Campaign Id","description":"The campaign's ID in Google Ads. Empty while a just-created campaign is still being pushed to Google.","example":"21098765432"},"campaign_name":{"type":"string","title":"Campaign Name","description":"Name shown for the campaign.","example":"Spring sale in Berlin"},"campaign_type":{"type":"string","enum":["SMART","PERFORMANCE_MAX","SEARCH","DISPLAY","SHOPPING","VIDEO","DEMAND_GEN","LOCAL","UNKNOWN"],"title":"Campaign Type","description":"Campaign type. Base44 creates `SMART` and `PERFORMANCE_MAX`. The other values appear only on campaigns created outside Base44 and synced in.","example":"SMART"},"status":{"type":"string","enum":["ENABLED","PAUSED","REMOVED","UNKNOWN"],"title":"Status","description":"Serving state in Base44's cache. A status of `REMOVED` is a deleted campaign, which Google keeps for reporting.","example":"ENABLED"},"daily_budget_micros":{"type":"integer","title":"Daily Budget Micros","description":"Daily budget in micros of the account currency, where 1,000,000 micros is one unit, so `15000000` is 15.00.","example":15000000},"landing_page":{"type":"string","title":"Landing Page","description":"URL the ads send clicks to.","example":"https://example.com/spring"},"geo_targets":{"items":{"type":"string"},"type":"array","title":"Geo Targets","description":"Google Ads geo target constant IDs the campaign targets.","example":["1003854"]},"phone_number":{"type":"string","title":"Phone Number","description":"Campaign phone number, empty when the campaign has none. E.164 on PERFORMANCE_MAX (where it is a call asset); as typed on SMART.","example":"+493012345678"},"learning_ends_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Learning Ends At","description":"When Smart Bidding's learning period is expected to end, about 14 days after creation, or `null` on a campaign that has not started learning.","example":"2026-09-08T11:20:00Z"},"review_status":{"type":"string","title":"Review Status","description":"Google's policy review status, passed through as Google reports it (`REVIEWED`, `UNDER_REVIEW`, …). Empty until the first sync after creation.","example":"REVIEWED"},"serving_status":{"type":"string","title":"Serving Status","description":"Google's serving status, passed through as Google reports it. Can still report review gating after policy review clears, so read it alongside `review_status`.","example":"SERVING"},"primary_status":{"type":"string","title":"Primary Status","description":"Google's summary of whether the campaign is serving well, passed through as Google reports it (`ELIGIBLE`, `LIMITED`, `NOT_SERVING`, …).","example":"ELIGIBLE"},"primary_status_reasons":{"items":{"type":"string"},"type":"array","title":"Primary Status Reasons","description":"Google's reasons behind `primary_status`, for example why a campaign is limited. Empty when there is nothing to explain.","example":["CAMPAIGN_BUDGET_LIMITED"]},"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"When the campaign was created in Base44.","example":"2026-08-25T11:20:00Z"},"updated_date":{"type":"string","format":"date-time","title":"Updated Date","description":"When Base44 last changed its record of the campaign.","example":"2026-08-25T14:05:00Z"}},"type":"object","required":["id","google_campaign_id","campaign_name","campaign_type","status","daily_budget_micros","landing_page","geo_targets","phone_number","review_status","serving_status","primary_status","primary_status_reasons","created_date","updated_date"],"title":"CampaignResource","description":"The campaign fields this API commits to."},"CampaignStatusResponse":{"properties":{"status":{"type":"string","title":"Status","description":"The action that was applied: `paused`, `resumed`, or `deleted`.","example":"paused"}},"type":"object","required":["status"],"title":"CampaignStatusResponse","description":"Acknowledgement returned by the single-campaign state changes."},"CancelRunResponse":{"properties":{"status":{"type":"string","title":"Status","description":"`cancelling` when the request was accepted. On a run that had already finished, its existing status instead, and nothing changed.","example":"cancelling"},"run_id":{"type":"string","title":"Run Id","description":"ID of the run.","example":"0195f2a1-4c3e-7b21-9f0d-2a5c8e1b4d77"}},"type":"object","required":["status","run_id"],"title":"CancelRunResponse","description":"Acknowledgement of a manual cancel request.\n\n``status`` is the run's status as observed at the moment of acknowledgement:\n``\"cancelling\"`` when we accepted the request and asked Temporal to cancel;\nthe run row will flip to ``\"cancelled\"`` once the interpreter writes its\nterminal row. For already-terminal runs we return their current status as\nan idempotent no-op so the optimistic UI doesn't flap."},"ChatTurnConversation":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"ID of the conversation.","example":"1f0c2b7a-9d51-4c3e-8a62-7b4d5e6f8a90"},"messages":{"anyOf":[{"items":{"$ref":"#/components/schemas/ConversationMessageSummary"},"type":"array"},{"type":"null"}],"title":"Messages","description":"The conversation's messages once the request finished, oldest first."}},"type":"object","title":"ChatTurnConversation"},"ChatTurnResponse":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Display name of the app.","example":"My CRM"},"status":{"anyOf":[{"$ref":"#/components/schemas/AppStatusResponse"},{"type":"null"}],"description":"The app's build status once the request returned. Read `state` to tell a finished turn from one that is still processing or that failed."},"conversation":{"anyOf":[{"$ref":"#/components/schemas/ChatTurnConversation"},{"type":"null"}],"description":"The app's conversation, including the messages this request produced."}},"type":"object","title":"ChatTurnResponse","description":"An app, with its conversation and build status."},"CheckpointSummary":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"ID of the checkpoint. Pass it as `checkpoint_id` to [Deploy an app](/api-reference/deploy-an-app) to deploy this saved version.","example":"6886b8d390dc7e2f4a2c91b3"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Name of the checkpoint.","example":"Add contact form"},"changes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Changes","description":"Summary of what changed in this checkpoint, or `null` if none was recorded.","example":"Added a contact form to the home page"},"preview_status":{"anyOf":[{"type":"string","enum":["pending","building","ready","failed"]},{"type":"null"}],"title":"Preview Status","description":"Build status of this checkpoint's preview. Either `pending`, `building`, `ready`, or `failed`, or `null` if no preview build was attempted.","example":"ready"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By","description":"Email of the user who created the checkpoint.","example":"developer@example.com"},"created_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created Date","description":"Time the checkpoint was created, as a UTC timestamp in ISO 8601 format.","example":"2026-08-01T09:15:00"},"last_deployed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Deployed At","description":"Time this checkpoint was last deployed to production, as a UTC timestamp in ISO 8601 format, or `null` if it has never been deployed.","example":"2026-08-02T14:30:00"},"git_commit_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Git Commit Hash","description":"Git commit hash of the code captured in this checkpoint, or `null` if it has no commit.","example":"a1b2c3d4e5f67890abcdef1234567890abcdef12"},"preview_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preview Url","description":"URL to preview this checkpoint's build, or `null` if the checkpoint has no build.","example":"https://preview.base44.app/6886b8d390dc7e2f4a2c91b3"},"is_github_sync":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Github Sync","description":"Whether the checkpoint came from a GitHub sync (`true`) or an in-app change (`false`).","example":false}},"type":"object","title":"CheckpointSummary","description":"A saved version of an app."},"CloneUrls":{"properties":{"https":{"type":"string","title":"Https","description":"URL to clone the repository over HTTPS.","example":"https://github.com/base44/lead-tracker.git"},"ssh":{"type":"string","title":"Ssh","description":"URL to clone the repository over SSH.","example":"git@github.com:base44/lead-tracker.git"},"gh_cli":{"type":"string","title":"Gh Cli","description":"Ready-to-run GitHub CLI clone command.","example":"gh repo clone base44/lead-tracker"}},"type":"object","required":["https","ssh","gh_cli"],"title":"CloneUrls","description":"Repository clone URLs in different formats."},"ConfigureEmailResponse":{"properties":{"domain":{"type":"string","title":"Domain","description":"The domain that was retried.","example":"example.com"},"configuration_status":{"type":"string","title":"Configuration Status","description":"Where setup stands after the retry. Only `active` sends mail. A retry starts the next step rather than finishing it, so this is often still a `pending_` value, meaning setup is in progress. The `failed_` values mean it stopped and you can try again.","example":"pending_domain_verification"},"last_check":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Check","description":"When Base44 last checked this domain's records, as a UTC timestamp in ISO 8601 format, or `null` before the first check.","example":"2026-08-26T09:12:44"}},"type":"object","required":["domain","configuration_status","last_check"],"title":"ConfigureEmailResponse","description":"Response for email domain configuration."},"ConnectRepoRequest":{"properties":{"org_name":{"type":"string","title":"Org Name","description":"GitHub username or organization to create the repository under, as returned in `login` by [List GitHub organizations](/api-reference/list-github-organizations).","example":"base44"},"repo_name":{"type":"string","title":"Repo Name","description":"Name for the new repository. 1 to 100 characters made of letters, digits, hyphens, underscores or periods, starting and ending with a letter or digit, and it must not already exist on the account.","example":"lead-tracker"},"installation_id":{"type":"string","title":"Installation Id","description":"ID of the Base44 GitHub App installation on that account, as returned in `installation_id` by [List GitHub organizations](/api-reference/list-github-organizations).","example":"58231904"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Description for the new repository. Defaults to the app's name.","example":"Sales lead tracker"}},"type":"object","required":["org_name","repo_name","installation_id"],"title":"ConnectRepoRequest","description":"Request to connect a repository."},"ConnectionStatus":{"properties":{"connected":{"type":"boolean","title":"Connected","description":"Whether the app still has its repository connection, including one whose `status` is `error` or `revoked`. The value is `false` only when the app never connected and when its repository was disconnected.","example":true},"repo_full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Repo Full Name","description":"Full repository name, as `owner/repo`, or `null` when `connected` is `false`.","example":"base44/lead-tracker"},"repo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Repo Url","description":"URL of the repository on GitHub, or `null` when `connected` is `false`.","example":"https://github.com/base44/lead-tracker"},"clone_urls":{"anyOf":[{"$ref":"#/components/schemas/CloneUrls"},{"type":"null"}],"description":"URLs and CLI command for cloning the repository, or `null` when `connected` is `false`."},"org_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Org Name","description":"GitHub username or organization that owns the repository, or `null` when `connected` is `false`.","example":"base44"},"status":{"anyOf":[{"$ref":"#/components/schemas/GitHubConnectionStatus"},{"type":"null"}],"description":"Health of the connection, or `null` when `connected` is `false`. A value of `active` works normally, `error` hit a technical failure, and `revoked` means the GitHub App access was withdrawn. The last two still report `connected: true`, so this is the field that tells you the connection needs repair.","example":"active"},"installation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Installation Id","description":"ID of the Base44 GitHub App installation backing this connection, or `null` when `connected` is `false` and on a connection stored without one.","example":"58231904"},"webhook_active":{"type":"boolean","title":"Webhook Active","description":"Whether the repository has the Base44 webhook that triggers automatic pulls. While this is `false` on a connected app, GitHub isn't telling Base44 about pushes, so nothing arrives until you pull.","default":false,"example":true},"connected_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Connected By User Id","description":"ID of the Base44 user who connected the repository, or `null` when `connected` is `false`.","example":"6820f3a4e7b91d003c45a1f0"}},"type":"object","required":["connected"],"title":"ConnectionStatus","description":"Current connection status for an app."},"ContentMode":{"type":"string","enum":["series","selection"],"title":"ContentMode"},"ContentPlan":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"ID of the content plan.","example":"8c1f9a2e-3b7d-4c5e-9f01-2a3b4c5d6e7f"},"app_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"App Id","description":"ID of the app the plan belongs to.","example":"6820f3a4e7b91d003c45a1f2"},"strategy":{"anyOf":[{"$ref":"#/components/schemas/ContentStrategy"},{"type":"null"}],"description":"The strategy and the per-platform posts."},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At","description":"Time the plan was created, as an ISO 8601 timestamp.","example":"2026-08-24T09:15:00+00:00"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At","description":"Time the plan last changed, as an ISO 8601 timestamp.","example":"2026-08-24T10:02:00+00:00"}},"type":"object","title":"ContentPlan"},"ContentPlanResponse":{"properties":{"plan":{"anyOf":[{"$ref":"#/components/schemas/ContentPlan"},{"type":"null"}],"description":"The app's content plan, or `null` if it has none."}},"type":"object","title":"ContentPlanResponse","description":"The app's social content plan after the change."},"ContentStrategy":{"properties":{"app_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"App Summary","description":"Short summary of what the app does.","example":"A CRM for freelancers who want to track leads without a spreadsheet."},"marketing_approach":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Marketing Approach","description":"The approach the content takes, extracted from the accepted strategy. Empty if the strategy text was empty when the plan was generated.","example":"Lead with the spreadsheet pain, then show the app solving it."},"platforms":{"items":{"$ref":"#/components/schemas/PlatformContentPlan"},"type":"array","title":"Platforms","description":"One entry per platform you approved, in the order you sent them."}},"type":"object","title":"ContentStrategy"},"ConversationDetail":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"ID of the conversation.","example":"1f0c2b7a-9d51-4c3e-8a62-7b4d5e6f8a90"},"messages":{"anyOf":[{"items":{"$ref":"#/components/schemas/ConversationMessageSummary"},"type":"array"},{"type":"null"}],"title":"Messages","description":"The requested window of messages, oldest first."}},"type":"object","title":"ConversationDetail","description":"A window of messages from an app's AI chat."},"ConversationMessageSummary":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"ID of the message.","example":"7f3a1c88-52d4-4a0e-9b31-2c6f0d8e4a19"},"role":{"anyOf":[{"type":"string","enum":["user","assistant","system"]},{"type":"null"}],"title":"Role","description":"Who produced the message. A `user` message is a prompt sent to the AI, an `assistant` message is the AI's reply, and a `system` message is a platform-generated note.","example":"assistant"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content","description":"Text of the message. Empty on assistant turns whose work is carried entirely by tool calls, and on internal diff messages.","example":"I added a contact form to the home page."},"file_urls":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"File Urls","description":"URLs of the files attached to the message, or `null` if it has none.","example":["https://example.com/mockup.png"]},"hidden":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Hidden","description":"Whether the message is internal and hidden from the chat in the app editor.","example":false},"checkpoint_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Checkpoint Id","description":"ID of the [checkpoint](/developers/references/app-management/get-started/concepts#checkpoints) this message produced, or `null` if it produced none. Pass it as `checkpoint_id` to [Deploy an app](/api-reference/deploy-an-app) to deploy that version.","example":"6886b8d390dc7e2f4a2c91b3"},"tool_calls":{"anyOf":[{"items":{"$ref":"#/components/schemas/ConversationToolCallSummary"},"type":"array"},{"type":"null"}],"title":"Tool Calls","description":"Tool calls the AI made on this message, or `null` on messages that made none. A call with `status` set to `waiting_for_user_input` is holding the turn open until it is answered."},"usage":{"anyOf":[{"$ref":"#/components/schemas/MessageUsageSummary"},{"type":"null"}],"description":"Tokens and credits this message consumed, or `null` on messages that consumed none."},"metadata":{"anyOf":[{"$ref":"#/components/schemas/MessageMetadataSummary"},{"type":"null"}],"description":"Who created the message and when."}},"type":"object","title":"ConversationMessageSummary"},"ConversationToolCallSummary":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"ID of the tool call. Pass it as `tool_call_id` to [Submit tool-call input](/api-reference/submit-tool-call-input) when `status` is `waiting_for_user_input`.","example":"toolu_01A9FJd3kP2mNqRs7VwXyZ4b"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Name of the tool the AI is calling.","example":"create_file"},"status":{"anyOf":[{"type":"string","enum":["running","success","error","stopped","waiting_for_user_input"]},{"type":"null"}],"title":"Status","description":"Where the tool call is. Either `running`, `success`, `error`, `stopped`, or `waiting_for_user_input`. The last one means the turn is paused until the call is answered.","example":"waiting_for_user_input"},"requires_user_input":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Requires User Input","description":"Whether this tool call has to be approved or rejected before the turn can continue.","example":true},"arguments_string":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Arguments String","description":"What the AI asked the tool to do, as a JSON object encoded in a string. Parse it to see the arguments before answering a call that is waiting. On a call whose `requires_user_input` is `true` the value is complete rather than shortened, which is the case that matters, because approving without reading it is approving blind. On any other call it can be cut to the first 500 characters. The one exception either way is the browser-typing tools, `local_browser_type` and `local_browser_press_key`, which always replace what was typed with `[redacted]` so a password or one-time code is never returned, waiting or not. Approving one of those means approving a value you cannot see. It is an empty string on a tool call that takes no arguments.","example":"{\"file_path\": \"src/pages/Home.jsx\", \"content\": \"export default function Home() {}\"}"}},"type":"object","title":"ConversationToolCallSummary","description":"A tool call on an assistant message."},"ConversionActionResource":{"properties":{"id":{"type":"string","title":"Id","description":"Base44's ID for the conversion action.","example":"68b1c0d4e7b91d003c45a1f6"},"google_action_id":{"type":"string","title":"Google Action Id","description":"Google Ads' own ID for the goal, as Google Ads reports it. Not the same as `id`.","example":"7654321"},"action_name":{"type":"string","title":"Action Name","description":"Name of the goal in Google Ads.","example":"Purchase"},"category":{"type":"string","title":"Category","description":"Google Ads conversion category, for example `PURCHASE`, `ADD_TO_CART`, `SUBMIT_LEAD_FORM`, or `SIGN_UP`.","example":"PURCHASE"},"action_type":{"type":"string","title":"Action Type","description":"Google Ads conversion type. `WEBPAGE` for the goals Base44 creates. Empty on goals created before Base44 recorded the type.","example":"WEBPAGE"},"conversion_id":{"type":"string","title":"Conversion Id","description":"The gtag conversion ID for the goal. Empty until Google publishes the tag.","example":"AW-123456789"},"conversion_label":{"type":"string","title":"Conversion Label","description":"The gtag conversion label for the goal. Empty until Google publishes the tag.","example":"AbCdEfGhIj"}},"type":"object","required":["id","google_action_id","action_name","category","action_type","conversion_id","conversion_label"],"title":"ConversionActionResource","description":"One conversion goal on the Google Ads account."},"ConversionMappingDeleteResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Outcome of the request, which is `deleted` once the mapping is gone.","example":"deleted"}},"type":"object","required":["status"],"title":"ConversionMappingDeleteResponse","description":"Outcome of deleting a conversion mapping."},"ConversionMappingResource":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the mapping. Pass this as `mapping_id` to update or delete it.","example":"68b1c0d4e7b91d003c45a1f7"},"account_id":{"type":"string","title":"Account Id","description":"ID of the Google Ads account the mapping belongs to.","example":"68b1c0d4e7b91d003c45a1f8"},"entity_name":{"type":"string","title":"Entity Name","description":"Name of the app entity whose write this goal belongs to, as returned by [List entity schemas](/api-reference/list-entity-schemas). It is empty on a mapping Base44 created from a conversion it verified firing in the app code, because a client-side conversion has no entity behind it.","example":"Order"},"trigger_action":{"type":"string","title":"Trigger Action","description":"Which write on the entity the goal belongs to. Base44 uses `create`, `update`, and `delete`, and stores any other value as sent.","example":"create"},"conversion_type":{"type":"string","title":"Conversion Type","description":"Which kind of conversion this is, using Google's category vocabulary such as `PURCHASE`, `ADD_TO_CART`, `BEGIN_CHECKOUT`, `SIGN_UP`, `LEAD`, or `CONTACT`. Empty when nothing was set for it. The value is stored as sent and not checked against Google's list.","example":"PURCHASE"},"conversion_action_id":{"type":"string","title":"Conversion Action Id","description":"Google Ads' own ID for the goal this mapping points at, as returned in `google_action_id` by [List conversion actions](/api-reference/list-google-ads-conversion-actions). Empty when nothing was set for it. A mapping can carry both this and `conversion_type`, and this one is matched first. If it matches no goal on the account, which happens when the goal was recreated in Google Ads under a new ID, the mapping falls back to matching on `conversion_type`.","example":"7654321"},"value_field":{"type":"string","title":"Value Field","description":"Field on the entity record holding the conversion amount. It is read only when writing the wiring instructions for the AI builder, which falls back to `amount` when this is empty.","example":"total_price"},"currency_field":{"type":"string","title":"Currency Field","description":"Field on the entity record holding the currency code. Same use as `value_field`, and the instructions fall back to `USD` when this is empty.","example":"currency"},"id_field":{"type":"string","title":"Id Field","description":"Field on the entity record holding the order ID Google dedupes on. Same use as `value_field`, and the instructions fall back to `id` when this is empty.","example":"order_number"},"is_enabled":{"type":"boolean","title":"Is Enabled","description":"Whether Base44 counts this mapping when it decides a campaign's conversion events are configured (`true`) or ignores it (`false`).","example":true},"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"When the mapping was created.","example":"2026-08-25T11:20:00Z"},"updated_date":{"type":"string","format":"date-time","title":"Updated Date","description":"When the mapping was last changed.","example":"2026-08-25T14:05:00Z"}},"type":"object","required":["id","account_id","entity_name","trigger_action","conversion_type","conversion_action_id","value_field","currency_field","id_field","is_enabled","created_date","updated_date"],"title":"ConversionMappingResource","description":"A conversion goal tied to an entity write in the app."},"ConversionStatRow":{"properties":{"campaign_id":{"type":"string","title":"Campaign Id","description":"The Google Ads campaign ID. This is not the Base44 campaign ID the campaign endpoints take, which is reported as `id` by [List campaigns](/api-reference/list-google-ads-campaigns).","example":"21458812345"},"conversions":{"type":"number","title":"Conversions","description":"Conversions recorded for the campaign in the window.","example":24.0},"conversions_value":{"type":"number","title":"Conversions Value","description":"Total value of those conversions, in the account's currency.","example":1830.0}},"type":"object","required":["campaign_id","conversions","conversions_value"],"title":"ConversionStatRow","description":"Conversion totals for one campaign."},"ConversionUploadResult":{"properties":{"status":{"type":"string","title":"Status","description":"Whether anything reached Google. The value is `success` when at least one conversion landed and `no_rows_uploaded` when none did, so it does not tell you the whole batch landed.","example":"success"},"total_submitted":{"type":"integer","title":"Total Submitted","description":"How many conversions you sent, counted before any of the checks below.","example":12},"duplicates_removed":{"type":"integer","title":"Duplicates Removed","description":"How many conversions were dropped as duplicates of another conversion in the same request, matching on order or click ID together with the goal and the timestamp.","example":1},"cross_request_duplicates":{"type":"integer","title":"Cross Request Duplicates","description":"How many conversions a previous request already reported. Base44 remembers every conversion it has sent, permanently, so a retried upload lands here instead of counting twice.","example":2},"skipped_no_click_id":{"type":"integer","title":"Skipped No Click Id","description":"How many conversions were dropped before Google saw them because they carried none of `gclid`, `gbraid`, or `wbraid`. Google requires one of the three.","example":1},"total_uploaded":{"type":"integer","title":"Total Uploaded","description":"How many conversions Google accepted.","example":8}},"type":"object","required":["status","total_submitted","duplicates_removed","cross_request_duplicates","skipped_no_click_id","total_uploaded"],"title":"ConversionUploadResult","description":"What happened to a batch of uploaded conversions."},"CreateEmailDomainRequest":{"properties":{"domain":{"type":"string","title":"Domain","description":"Domain to send mail from. It has to already be connected to this app.","example":"example.com"},"sender_name":{"type":"string","title":"Sender Name","description":"Name recipients see in the From line.","example":"Nordwind Furniture"},"from_email":{"type":"string","format":"email","title":"From Email","description":"Address mail is sent from. Its domain has to be the `domain` you're enabling.","example":"no-reply@example.com"}},"type":"object","required":["domain","sender_name","from_email"],"title":"CreateEmailDomainRequest","description":"Request to create email domain configuration."},"CreateEmailDomainResponse":{"properties":{"domain":{"type":"string","title":"Domain","description":"The domain that was enabled.","example":"example.com"},"status":{"type":"string","title":"Status","description":"Where setup got to. Only `active` sends mail. The `pending_` values mean setup is still in progress, and the `failed_` values mean it stopped and you can start it again with [Retry email domain setup](/api-reference/retry-email-domain-setup).","example":"pending_user_dns_configuration"},"email_domain_id":{"type":"string","title":"Email Domain Id","description":"ID of this app's email configuration. It identifies the configuration, not the individual domain.","example":"68b1c0d4e7b91d003c45a1f2"},"external":{"type":"boolean","title":"External","description":"Whether you brought the domain yourself (`true`) or bought it through Base44 (`false`).","default":false,"example":true},"dns_records":{"anyOf":[{"items":{"$ref":"#/components/schemas/EmailDnsRecordResponse"},"type":"array"},{"type":"null"}],"title":"Dns Records","description":"Records to publish for this domain. They come back even when Base44 publishes them for you, so you can pass them to whoever manages the domain's DNS.","example":[{"name":"em1234.example.com","status":"pending","ttl":300,"type":"CNAME","value":"u1234567.wl123.sendgrid.net"}]},"provider_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Id","description":"Identifier for the registrar the domain sits with, when Base44 knows it.","example":"godaddy"}},"type":"object","required":["domain","status","email_domain_id"],"title":"CreateEmailDomainResponse","description":"Response for creating email domain configuration."},"CreateEntitySchemaRequest":{"properties":{"entity_name":{"type":"string","title":"Entity Name","description":"Name for the new entity. Letters, numbers, and underscores only. Cannot be `User`.","example":"Invoice"},"entity_schema":{"additionalProperties":true,"type":"object","title":"Entity Schema","description":"The entity's [JSON Schema](/developers/backend/resources/entities/entity-schemas). Needs `\"type\": \"object\"` and a `properties` object, plus any `required` fields and [row-level security rules](/developers/backend/resources/entities/security) under `rls`.","example":{"name":"Invoice","properties":{"amount":{"description":"Total amount in cents","type":"number"},"status":{"enum":["draft","sent","paid"],"type":"string"}},"required":["amount"],"rls":{"read":{"created_by":"{{user.email}}"}},"type":"object"}}},"type":"object","required":["entity_name","entity_schema"],"title":"CreateEntitySchemaRequest"},"CreateManualCheckpointRequest":{"properties":{"name":{"type":"string","title":"Name","description":"Name for the checkpoint, shown in the app's version history. Send a non-empty value: an empty string is not stored, and the checkpoint is named after the app's most recent chat message instead.","default":"Manual Edits","example":"Before pricing rework"}},"type":"object","title":"CreateManualCheckpointRequest"},"CreateWorkflowRequest":{"properties":{"name":{"type":"string","title":"Name","description":"Name for the workflow. Must be unique among the app's workflows that are not archived.","example":"Email me new signups"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"What the workflow is for, in your own words.","example":"Sends an email whenever a User record is created."},"definition":{"additionalProperties":true,"type":"object","title":"Definition","description":"The steps to run, as a CNCF Serverless Workflow v1.0 document. Check it with [Validate a workflow definition](/api-reference/validate-a-workflow-definition) first.","example":{"do":[],"document":{"dsl":"1.0.0","name":"notify","version":"1.0.0"}}},"trigger":{"additionalProperties":true,"type":"object","title":"Trigger","description":"What starts the workflow. The trigger goes inside `config`, whose `trigger_type` picks the kind and whose remaining fields configure it. Add a top-level `condition` to skip a dispatch unless a jq expression over the payload is truthy.","example":{"config":{"cron_expression":"0 9 * * *","timezone":"UTC","trigger_type":"scheduled"}}},"change_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Change Summary","description":"Note describing this version, kept in the workflow's version history.","example":"Initial version"}},"type":"object","required":["name","definition","trigger"],"title":"CreateWorkflowRequest"},"CustomDomainResource":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the domain. Pass it as `domain_id` to the other domain endpoints.","example":"68b1c0d4e7b91d003c45a1f7"},"domain":{"type":"string","title":"Domain","description":"The domain name, normalized to lower case with any scheme, trailing slash and leading `www.` removed.","example":"example.com"},"app_id":{"type":"string","title":"App Id","description":"ID of the app the domain serves.","example":"6820f3a4e7b91d003c45a1f2"},"disabled":{"type":"boolean","title":"Disabled","description":"Whether Base44 has stopped serving the domain. A disabled domain stays attached to the app and keeps its DNS, and serves a placeholder page instead of the app.","example":false},"last_status_check":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Status Check","description":"When [Get custom domain status](/api-reference/get-custom-domain-status) last ran for this domain, as a UTC timestamp in ISO 8601 format, or `null` if it never has.","example":"2026-08-15T09:10:00"},"last_status_payload":{"anyOf":[{"$ref":"#/components/schemas/CustomDomainStatusPayload"},{"type":"null"}],"description":"The provider's last report on the domain, or `null` until a status check has run."},"redirect_target_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Redirect Target Domain","description":"The domain that visitors to this one are sent to with a 301 redirect. The value is `null` when this domain serves the app itself.","example":"www.example.com"},"provider_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Id","description":"Names the registrar when the domain was bought through Base44's Wix flow. The value is `null` otherwise, which covers both a domain you own elsewhere and one bought through the Entri flow, so this isn't a purchased-versus-external signal.","example":"wix"}},"type":"object","required":["id","domain","app_id","disabled"],"title":"CustomDomainResource","description":"A custom domain attached to an app."},"CustomDomainStatusPayload":{"properties":{"verificationStatus":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verificationstatus","description":"Whether the provider has verified the domain's DNS. Only `verified` means the domain serves the app, and anything else means it doesn't yet. Absent until [Get custom domain status](/api-reference/get-custom-domain-status) has run once.","example":"verified"}},"type":"object","title":"CustomDomainStatusPayload","description":"What the hosting provider last reported about the domain."},"DailyMetrics":{"properties":{"date":{"type":"string","title":"Date","description":"The day these totals cover, as `YYYY-MM-DD` in UTC.","example":"2026-08-25"},"payments":{"type":"integer","title":"Payments","description":"Paid that day, in the smallest unit of `currency`, so `125000` is 1,250.00 in a currency with two decimal places.","default":0,"example":8200},"payment_count":{"type":"integer","title":"Payment Count","description":"Payments taken that day.","default":0,"example":4},"refunds":{"type":"integer","title":"Refunds","description":"Refunded that day, in the smallest unit of `currency`, so `125000` is 1,250.00 in a currency with two decimal places.","default":0,"example":0},"refund_count":{"type":"integer","title":"Refund Count","description":"Refunds issued that day.","default":0,"example":0},"disputes":{"type":"integer","title":"Disputes","description":"Lost to disputes that day, in the smallest unit of `currency`, so `125000` is 1,250.00 in a currency with two decimal places.","default":0,"example":0},"dispute_count":{"type":"integer","title":"Dispute Count","description":"Disputes the customer won that day.","default":0,"example":0},"unique_customers":{"type":"integer","title":"Unique Customers","description":"Distinct customers who paid that day.","default":0,"example":4}},"type":"object","required":["date"],"title":"DailyMetrics","description":"Payment totals for one day."},"DashboardCampaignRow":{"properties":{"id":{"type":"string","title":"Id","description":"The Google Ads campaign ID. This is not the Base44 campaign ID the campaign endpoints take, which is reported as `id` by [List campaigns](/api-reference/list-google-ads-campaigns).","example":"21458812345"},"name":{"type":"string","title":"Name","description":"Name of the campaign.","example":"Spring sale - Search"},"status":{"type":"string","title":"Status","description":"Google Ads campaign status, one of `ENABLED`, `PAUSED`, or `REMOVED`.","example":"ENABLED"},"campaign_type":{"type":"string","title":"Campaign Type","description":"Google Ads channel type, for example `SEARCH`, `PERFORMANCE_MAX`, or `SMART`.","example":"SEARCH"},"spend":{"type":"number","title":"Spend","description":"Amount spent in the window, in the account's currency.","example":412.5},"impressions":{"type":"integer","title":"Impressions","description":"Impressions in the window.","example":18400},"clicks":{"type":"integer","title":"Clicks","description":"Clicks in the window.","example":612},"conversions":{"type":"number","title":"Conversions","description":"Conversions in the window.","example":24.0},"conversions_value":{"type":"number","title":"Conversions Value","description":"Total value of those conversions, in the account's currency.","example":1830.0},"ctr":{"type":"number","title":"Ctr","description":"Click-through rate for the window, as a percentage. `0` when the campaign had no impressions.","example":3.33},"trend":{"anyOf":[{"items":{"$ref":"#/components/schemas/DashboardTrendPoint"},"type":"array"},{"type":"null"}],"title":"Trend","description":"Daily series for this campaign. Present when `include_campaign_trend` is true, and always present when Base44 served its own copy of the numbers, which builds it regardless of the flag.","example":[{"clicks":48,"conversions":3.0,"date":"2026-08-14"}]},"conversion_goals":{"anyOf":[{"items":{"$ref":"#/components/schemas/DashboardConversionGoal"},"type":"array"},{"type":"null"}],"title":"Conversion Goals","description":"This campaign's share of each conversion goal. Absent when Base44 served its own copy of the numbers, and `null` when the goal read failed.","example":[{"category":"PURCHASE","conversions":12.0,"name":"Purchase"}]}},"type":"object","required":["id","name","status","campaign_type","spend","impressions","clicks","conversions","conversions_value","ctr"],"title":"DashboardCampaignRow","description":"One campaign's totals for the window."},"DashboardConversionGoal":{"properties":{"category":{"type":"string","title":"Category","description":"Google Ads conversion category, for example `PURCHASE` or `SUBMIT_LEAD_FORM`.","example":"PURCHASE"},"name":{"type":"string","title":"Name","description":"Name of the goal as Google Ads reports it.","example":"Purchase"},"conversions":{"type":"number","title":"Conversions","description":"Conversions recorded against this goal in the window. Goals that recorded nothing are still listed, with `0`.","example":12.0}},"type":"object","required":["category","name","conversions"],"title":"DashboardConversionGoal","description":"One conversion goal on the account, with its count for the window."},"DashboardResponse":{"properties":{"spend":{"type":"number","title":"Spend","description":"Amount spent in the window, in the account's currency.","example":1240.75},"spend_trend":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Spend Trend","description":"Percentage change in spend against the previous window of the same length. `null` when the previous window had no spend to compare against.","example":12.4},"impressions":{"type":"integer","title":"Impressions","description":"Impressions in the window.","example":52100},"impressions_trend":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Impressions Trend","description":"Percentage change in impressions against the previous window.","example":-3.2},"clicks":{"type":"integer","title":"Clicks","description":"Clicks in the window.","example":1830},"clicks_trend":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Clicks Trend","description":"Percentage change in clicks against the previous window.","example":8.1},"conversions":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Conversions","description":"Conversions in the window. `null` when the account has no conversion tracking configured and recorded none, because the number would read as a real zero.","example":64.0},"conversions_trend":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Conversions Trend","description":"Percentage change in conversions against the previous window. `null` whenever `conversions` is `null`.","example":15.0},"roas":{"type":"number","title":"Roas","description":"Return on ad spend: conversion value divided by spend. `0` when nothing was spent.","example":2.35},"roas_trend":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Roas Trend","description":"Percentage change in return on ad spend against the previous window.","example":4.7},"campaign_breakdown":{"items":{"$ref":"#/components/schemas/DashboardCampaignRow"},"type":"array","title":"Campaign Breakdown","description":"One row per campaign that has metrics in the window, archived campaigns included, so the rows sum to the totals above.","example":[{"campaign_type":"SEARCH","clicks":612,"conversions":24.0,"conversions_value":1830.0,"ctr":3.33,"id":"21458812345","impressions":18400,"name":"Spring sale - Search","spend":412.5,"status":"ENABLED"}]},"top_search_terms":{"items":{"$ref":"#/components/schemas/DashboardTopSearchTerm"},"type":"array","title":"Top Search Terms","description":"Up to four search terms that drove the most clicks in the last 30 days, regardless of the window you asked for. Empty when the term read failed or the account has no campaigns yet.","example":[{"clicks":17,"term":"oak dining table"}]},"monthly_spend_cap_micros":{"type":"integer","title":"Monthly Spend Cap Micros","description":"The account's monthly spend cap in micros, or `0` when no cap is set. Absent when the account has no synced campaigns yet.","default":0,"example":50000000},"includes_estimated":{"type":"boolean","title":"Includes Estimated","description":"`true` when some rows are Base44's own estimate rather than a figure Google reported.","example":false},"trend":{"anyOf":[{"items":{"$ref":"#/components/schemas/DashboardTrendPoint"},"type":"array"},{"type":"null"}],"title":"Trend","description":"Account-wide daily series for the window. Absent when Base44 served its own copy of the numbers instead of Google's live figures.","example":[{"clicks":48,"conversions":3.0,"date":"2026-08-14"}]},"conversion_goals":{"anyOf":[{"items":{"$ref":"#/components/schemas/DashboardConversionGoal"},"type":"array"},{"type":"null"}],"title":"Conversion Goals","description":"Every conversion goal on the account with its count for the window. Absent when Base44 served its own copy of the numbers, and `null` when the goal read failed.","example":[{"category":"PURCHASE","conversions":12.0,"name":"Purchase"}]}},"type":"object","required":["spend","impressions","clicks","roas","campaign_breakdown","includes_estimated"],"title":"DashboardResponse","description":"Account-wide totals for the window, plus a per-campaign breakdown."},"DashboardTopSearchTerm":{"properties":{"term":{"type":"string","title":"Term","description":"The search term someone typed.","example":"oak dining table"},"clicks":{"type":"integer","title":"Clicks","description":"Clicks the term drove in the last 30 days.","example":17}},"type":"object","required":["term","clicks"],"title":"DashboardTopSearchTerm","description":"One of the search terms that drove the most clicks in the last 30 days."},"DashboardTrendPoint":{"properties":{"date":{"type":"string","title":"Date","description":"The day, as `YYYY-MM-DD`.","example":"2026-08-14"},"clicks":{"type":"integer","title":"Clicks","description":"Clicks across the account that day.","example":48},"conversions":{"type":"number","title":"Conversions","description":"Conversions across the account that day.","example":3.0}},"type":"object","required":["date","clicks","conversions"],"title":"DashboardTrendPoint","description":"One day of account-wide activity."},"DeleteEmailResponse":{"properties":{},"type":"object","title":"DeleteEmailResponse","description":"Response for deleting email domain."},"DeleteEntitySchemaResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Always `true`. A delete that failed returns an error status instead.","example":true}},"type":"object","required":["success"],"title":"DeleteEntitySchemaResponse","description":"Confirmation that an entity was removed from the app."},"DeployRequest":{"properties":{"checkpoint_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Checkpoint Id","description":"ID of a specific saved version of the app to deploy. If omitted, the app's current version is deployed.","example":"6886b8d390dc7e2f4a2c91b3"}},"type":"object","title":"DeployRequest"},"DeployResponse":{"properties":{"app_id":{"type":"string","title":"App Id","description":"ID of the app that was deployed.","example":"6820f3a4e7b91d003c45a1f2"},"checkpoint_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Checkpoint Id","description":"ID of the saved version associated with this deploy, or `null` if the app has no saved versions. The git commit hash identifies the exact code that was deployed.","example":"6886b8d390dc7e2f4a2c91b3"},"git_commit_hash":{"type":"string","title":"Git Commit Hash","description":"Git commit hash of the code that was deployed.","example":"a1b2c3d4e5f67890abcdef1234567890abcdef12"},"deployed_at":{"type":"string","format":"date-time","title":"Deployed At","description":"Time the app was deployed, as a UTC timestamp in ISO 8601 format.","example":"2026-08-02T14:30:00"}},"type":"object","required":["app_id","checkpoint_id","git_commit_hash","deployed_at"],"title":"DeployResponse","description":"The version of an app that was published, and when."},"DisconnectAccountResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Outcome of the request, which is `disconnected` once it is recorded.","example":"disconnected"},"budget_end_confirmed":{"type":"boolean","title":"Budget End Confirmed","description":"Whether Google confirmed the account's budget was ended as part of the disconnect. A value of `false` means the disconnect is recorded but the budget wind-down is still in progress.","example":true}},"type":"object","required":["status","budget_end_confirmed"],"title":"DisconnectAccountResponse","description":"Outcome of disconnecting the Google Ads account."},"DomainMessageResponse":{"properties":{"message":{"type":"string","title":"Message","description":"What happened, in plain text.","example":"Domain successfully unlinked"}},"type":"object","required":["message"],"title":"DomainMessageResponse","description":"A one-line confirmation."},"DurableAssetRow":{"properties":{"resource_name":{"type":"string","title":"Resource Name","description":"Google Ads resource name of the link between the asset and the asset group.","example":"customers/1234567890/assetGroupAssets/1122334455~98765432~HEADLINE"},"field_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Field Type","description":"Which slot the asset fills, as Google reports it, or `null` when Google returned no field type.","example":"HEADLINE"},"primary_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Status","description":"Google's serving status for the asset, such as `ELIGIBLE`, `LIMITED` or `PENDING`. The value is `null` when Google returned no status.","example":"ELIGIBLE"}},"type":"object","required":["resource_name","field_type","primary_status"],"title":"DurableAssetRow","description":"One asset already attached to a campaign in Google Ads."},"EmailDnsRecordResponse":{"properties":{"type":{"type":"string","title":"Type","description":"Record type. Either `CNAME`, `TXT` or `MX`.","example":"CNAME"},"name":{"type":"string","title":"Name","description":"Host the record goes on.","example":"em1234.example.com"},"value":{"type":"string","title":"Value","description":"Value to publish.","example":"u1234567.wl123.sendgrid.net"},"ttl":{"type":"integer","title":"Ttl","description":"Time to live to publish the record with, in seconds.","default":300,"example":300},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Whether Base44 can see the record yet. Either `pending`, `verified` or `failed`. The value is `null` before the first check.","example":"pending"}},"type":"object","required":["type","name","value"],"title":"EmailDnsRecordResponse","description":"DNS record for email configuration."},"EmailDomainInfo":{"properties":{"id":{"type":"string","title":"Id","description":"ID of this app's email configuration. Every domain in the list repeats it, so tell entries apart by `domain` rather than by this.","example":"68b1c0d4e7b91d003c45a1f2"},"domain":{"type":"string","title":"Domain","description":"The domain mail is sent from.","example":"example.com"},"sender_name":{"type":"string","title":"Sender Name","description":"Name recipients see in the From line.","example":"Nordwind Furniture"},"from_email":{"type":"string","title":"From Email","description":"Address mail is sent from.","example":"no-reply@example.com"},"configuration_status":{"type":"string","title":"Configuration Status","description":"Where setup stands. Only `active` sends mail. The `pending_` values mean setup is still in progress, and the `failed_` values mean it stopped and you can start it again with [Retry email domain setup](/api-reference/retry-email-domain-setup).","example":"active"},"enabled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Enabled At","description":"When the domain finished verifying, as a UTC timestamp in ISO 8601 format, or `null` if it hasn't verified yet.","example":"2026-08-20T16:31:00"},"suspended_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Suspended Reason","description":"Why sending was suspended, when it was. The value is `null` on a domain in good standing.","example":"Repeated spam complaints"},"external":{"type":"boolean","title":"External","description":"Whether you brought the domain yourself (`true`) or bought it through Base44 (`false`).","default":false,"example":true},"dns_records":{"items":{"$ref":"#/components/schemas/EmailDnsRecordResponse"},"type":"array","title":"Dns Records","description":"Records this domain needs, with whether Base44 can see each one yet.","default":[],"example":[{"name":"em1234.example.com","status":"verified","ttl":300,"type":"CNAME","value":"u1234567.wl123.sendgrid.net"}]},"provider_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Id","description":"Identifier for the registrar the domain sits with, when Base44 knows it.","example":"godaddy"}},"type":"object","required":["id","domain","sender_name","from_email","configuration_status","enabled_at"],"title":"EmailDomainInfo","description":"Email domain info for get endpoint."},"EnhancePromptRequest":{"properties":{"raw_prompt":{"type":"string","title":"Raw Prompt","description":"The image prompt to rewrite, in the caller's own words. Empty or whitespace-only returns an empty result without running a model.","default":"","example":"oak table photo"}},"type":"object","title":"EnhancePromptRequest"},"EnhancedConversionUploadResult":{"properties":{"status":{"type":"string","title":"Status","description":"Whether anything reached Google. The value is `success` when at least one adjustment landed and `no_rows_uploaded` when none did, so it does not tell you the whole batch landed.","example":"success"},"total_submitted":{"type":"integer","title":"Total Submitted","description":"How many adjustments you sent.","example":5},"total_uploaded":{"type":"integer","title":"Total Uploaded","description":"How many adjustments Google accepted. Anything short of `total_submitted` is made up of adjustments dropped for carrying no usable hashed identifier and adjustments Google rejected one by one, and the response does not separate the two.","example":4}},"type":"object","required":["status","total_submitted","total_uploaded"],"title":"EnhancedConversionUploadResult","description":"What happened to a batch of enhanced-conversion adjustments."},"EntityCountResponse":{"properties":{"count":{"type":"integer","title":"Count","description":"Number of records you can read in this entity.","example":128}},"type":"object","required":["count"],"title":"EntityCountResponse","description":"The number of records stored in one of an app's entities."},"EntitySchema":{"properties":{"entity_name":{"type":"string","title":"Entity Name","description":"Name of the entity. The name `User` is the built-in user entity, and every other name is one the app defines.","example":"Invoice"},"entity_schema":{"additionalProperties":true,"type":"object","title":"Entity Schema","description":"The entity's stored [JSON Schema](/developers/backend/resources/entities/entity-schemas), including its `properties`, `required` fields, and any [row-level security rules](/developers/backend/resources/entities/security) under `rls`. For the app's own entities it also carries a `name` key holding the entity name. For `User` it holds only the custom fields added on top of the built-in ones, and has no `name` key.","example":{"name":"Invoice","properties":{"amount":{"description":"Total amount in cents","type":"number"},"status":{"enum":["draft","sent","paid"],"type":"string"}},"required":["amount"],"rls":{"read":{"created_by":"{{user.email}}"}},"type":"object"}}},"type":"object","required":["entity_name","entity_schema"],"title":"EntitySchema"},"EntitySchemaResponse":{"properties":{"entity_name":{"type":"string","title":"Entity Name","description":"Name of the entity.","example":"Invoice"},"entity_schema":{"additionalProperties":true,"type":"object","title":"Entity Schema","description":"The entity's stored [JSON Schema](/developers/backend/resources/entities/entity-schemas), including its `properties`, `required` fields, and any [row-level security rules](/developers/backend/resources/entities/security) under `rls`. For the app's own entities it also carries a `name` key holding the entity name. For `User` it holds only the custom fields added on top of the built-in ones, and has no `name` key.","example":{"name":"Invoice","properties":{"amount":{"description":"Total amount in cents","type":"number"},"status":{"enum":["draft","sent","paid"],"type":"string"}},"required":["amount"],"rls":{"read":{"created_by":"{{user.email}}"}},"type":"object"}}},"type":"object","required":["entity_name","entity_schema"],"title":"EntitySchemaResponse","description":"One of an app's entities and its stored JSON Schema."},"EventNameInfo":{"properties":{"name":{"type":"string","title":"Name","description":"Name of the event, as the app tracked it.","example":"checkout_completed"},"count":{"type":"integer","title":"Count","description":"Number of times the event was recorded across the retained history.","example":1842}},"type":"object","required":["name","count"],"title":"EventNameInfo","description":"Information about a tracked event name."},"EventNamesResponse":{"properties":{"event_names":{"items":{"$ref":"#/components/schemas/EventNameInfo"},"type":"array","title":"Event Names","description":"The app's event names, most frequent first.","example":[{"count":15320,"name":"page_view"},{"count":1842,"name":"checkout_completed"}]}},"type":"object","title":"EventNamesResponse","description":"The analytics event names an app has recorded."},"EventPropertyKeysResponse":{"properties":{"event_name":{"type":"string","title":"Event Name","description":"Name of the event the property keys belong to.","example":"checkout_completed"},"property_keys":{"items":{"type":"string"},"type":"array","title":"Property Keys","description":"Property keys the app has sent with this event. Prefix a key with `properties.` to filter, aggregate, or group by it, which works for keys made up of letters, digits, and underscores.","example":["plan","amount","currency"]}},"type":"object","required":["event_name"],"title":"EventPropertyKeysResponse","description":"The property keys an app has sent with one analytics event."},"GenerateAssetsResult":{"properties":{"session_id":{"type":"string","title":"Session Id","description":"ID of this generation. Pass it as `session_id` to [List Google Ads generated assets](/api-reference/list-google-ads-generated-assets) to poll for the images that are still coming.","example":"9c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f"},"text":{"items":{"$ref":"#/components/schemas/GeneratedAssetSummary"},"type":"array","title":"Text","description":"The copy that was generated. Empty when the text engine produced nothing and when a generation for this app was already running.","example":[]},"images":{"items":{"$ref":"#/components/schemas/GeneratedAssetSummary"},"type":"array","title":"Images","description":"The pictures that finished in time to be returned inline. The rest are counted in `background_image_count`.","example":[]},"background_image_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Background Image Count","description":"How many more pictures are still being generated for this session. The response comes back as soon as the copy is done, so poll [List Google Ads generated assets](/api-reference/list-google-ads-generated-assets) with `session_id` for these. The field is absent when a generation for this app was already running, which is how you tell that case apart.","example":3},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"Language the copy was written in, as a lowercase two-letter code. It comes from the app's own site rather than from anything you send. The field is absent when a generation for this app was already running.","example":"de"}},"type":"object","required":["session_id","text","images"],"title":"GenerateAssetsResult","description":"The creative one generation produced."},"GenerateCopyRequest":{"properties":{"business_name":{"type":"string","title":"Business Name","description":"Name of the business the ads are for.","example":"Nordwind Furniture"},"business_description":{"type":"string","title":"Business Description","description":"What the business sells, in a sentence or two. The more specific this is, the better the copy.","default":"","example":"Handmade solid oak dining tables, built to order in San Francisco."},"keywords":{"items":{"type":"string"},"type":"array","title":"Keywords","description":"Words the copy should lean on.","example":["oak dining table","handmade furniture"]},"landing_page_url":{"type":"string","title":"Landing Page Url","description":"Page the ads send people to. Used as context for the copy.","default":"","example":"https://nordwind-furniture.com"},"campaign_goal":{"type":"string","title":"Campaign Goal","description":"What the campaign is for: `traffic`, `leads` or `sales`.","default":"traffic","example":"traffic"}},"type":"object","required":["business_name"],"title":"GenerateCopyRequest"},"GenerateImageAssetsResult":{"properties":{"session_id":{"type":"string","title":"Session Id","description":"ID of this generation. Pass it as `session_id` to [List Google Ads generated assets](/api-reference/list-google-ads-generated-assets) to collect the pictures that are still coming.","example":"9c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f"},"assets":{"items":{"$ref":"#/components/schemas/GeneratedAssetSummary"},"type":"array","title":"Assets","description":"The pictures that finished in time to be returned inline. Empty when nothing landed in time, when the engine produced nothing, and when a generation for this app was already running, which `in_flight` tells apart.","example":[]},"background_pending":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Background Pending","description":"Present and `true` when more pictures are still being generated under this `session_id`, including when `assets` came back empty because the first one took too long. Absent on a lock-miss response.","example":true},"background_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Background Count","description":"How many more pictures are still being generated. Absent on a lock-miss response.","example":3},"in_flight":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"In Flight","description":"Present and `true` only when an image generation for this app was already running, in which case `session_id` is that run's and `assets` is empty. Poll the returned `session_id` rather than retrying. The field is absent on every other response.","example":true}},"type":"object","required":["session_id","assets"],"title":"GenerateImageAssetsResult","description":"The pictures one image generation produced."},"GenerateImagePayload":{"properties":{"image_prompt":{"type":"string","maxLength":2000,"title":"Image Prompt","description":"Prompt to generate the image from. Used only when the post carries no `image_prompt` of its own, which is the prompt the plan generated for it. Send the post's own `image_prompt` back if you want to be sure of what is used.","example":"A freelancer closing a laptop at a tidy desk, warm morning light"},"refinement_instruction":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Refinement Instruction","description":"What to change about the existing image. Sending this regenerates the image even when the post already has one. The first 300 characters are used.","example":"Make the lighting cooler and remove the coffee cup."}},"type":"object","required":["image_prompt"],"title":"GenerateImagePayload"},"GeneratePlanPayload":{"properties":{"platforms":{"items":{"type":"string"},"type":"array","maxItems":3,"minItems":1,"title":"Platforms","description":"Platforms to generate content for, 1 to 3 of `x`, `instagram`, `tiktok`, `linkedin`, `reddit`, `facebook`. The order is preserved in the response. Unsupported values are ignored, and a request where none are supported fails with a 400.","example":["instagram","linkedin"]},"social_url":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Social Url","description":"HTTPS URL of your social profile, used to match the writing voice of the generated content. Omit it to reuse the voice resolved when you submitted answers.","example":"https://x.com/yourhandle"}},"type":"object","required":["platforms"],"title":"GeneratePlanPayload"},"GenerateTextAssetsResult":{"properties":{"session_id":{"type":"string","title":"Session Id","description":"ID of this generation. Pass it as `session_id` to [List Google Ads generated assets](/api-reference/list-google-ads-generated-assets), or to [Accept a generated Google Ads asset](/api-reference/accept-a-generated-google-ads-asset) with one of the returned `session_asset_id` values.","example":"9c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f"},"assets":{"items":{"$ref":"#/components/schemas/GeneratedAssetSummary"},"type":"array","title":"Assets","description":"The copy that was generated, one entry per line. Empty when the engine produced nothing and when a generation for this app was already running, which `in_flight` tells apart.","example":[]},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"Language the copy was written in, as a lowercase two-letter code. It comes from the app's own site rather than from anything you send. The field is absent on a lock-miss response unless you asked for a specific campaign.","example":"de"},"in_flight":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"In Flight","description":"Present and `true` only when a text generation for this app was already running, in which case `session_id` is that run's and `assets` is empty. Poll the returned `session_id` rather than retrying. The field is absent on every other response.","example":true}},"type":"object","required":["session_id","assets"],"title":"GenerateTextAssetsResult","description":"The copy one text generation produced."},"GeneratedAssetSummary":{"properties":{"session_asset_id":{"type":"string","title":"Session Asset Id","description":"ID of this asset within the session. Pass it as `session_asset_id` to [Accept a generated Google Ads asset](/api-reference/accept-a-generated-google-ads-asset).","example":"b7f3a1c8-52d4-4a0e-9b31-2c6f0d8e4a19"},"asset_field_type":{"type":"string","title":"Asset Field Type","description":"Which slot on the ad this asset fills. Text assets are `HEADLINE`, `LONG_HEADLINE` or `DESCRIPTION`. Image assets are `MARKETING_IMAGE`, `SQUARE_MARKETING_IMAGE`, `PORTRAIT_MARKETING_IMAGE` or `TALL_PORTRAIT_MARKETING_IMAGE`.","example":"HEADLINE"},"kind":{"type":"string","title":"Kind","description":"Whether the asset is copy (`text`) or a picture (`image`).","example":"text"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text","description":"The generated copy, or `null` on an image asset.","example":"Handmade oak furniture, built to last"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url","description":"URL of the generated picture, or `null` on a text asset. It is a Base44 preview URL, not a Google one, and it stops resolving once the asset is cleaned up.","example":"https://storage.base44.com/gads-assets/b7f3a1c8-square.png"},"source":{"type":"string","title":"Source","description":"Which engine wrote it. The value is `google` for Google's own asset generation and `inhouse` for the Base44 model that covers languages Google does not generate for, and that stands in when Google's call fails.","example":"google"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"Language of a text asset as a lowercase two-letter code, or `null` on an image, because image assets carry no copy.","example":"de"},"channel_type":{"type":"string","title":"Channel Type","description":"Campaign type the asset was generated for, one of `SEARCH`, `PERFORMANCE_MAX`, `DISPLAY` or `DEMAND_GEN`.","example":"PERFORMANCE_MAX"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the asset was generated.","example":"2026-08-25T14:05:00Z"}},"type":"object","required":["session_asset_id","asset_field_type","kind","text","image_url","source","language","channel_type","created_at"],"title":"GeneratedAssetSummary","description":"One generated asset in a live generation session."},"GeneratedAssetsList":{"properties":{"session":{"anyOf":[{"items":{"$ref":"#/components/schemas/GeneratedAssetSummary"},"type":"array"},{"type":"null"}],"title":"Session","description":"Assets in the generation session you asked for. Present only when you send `session_id`, and an empty list while the generation is still running or when it produced nothing.","example":[]},"library":{"anyOf":[{"items":{"$ref":"#/components/schemas/LibraryAssetSummary"},"type":"array"},{"type":"null"}],"title":"Library","description":"The app's generated assets, newest first, capped at the 5,000 most recent. Present only when you omit `session_id`. There is no pagination and nothing marks a truncated list, so an app past 5,000 assets silently stops seeing its oldest ones.","example":[]},"durable":{"anyOf":[{"items":{"$ref":"#/components/schemas/DurableAssetRow"},"type":"array"},{"type":"null"}],"title":"Durable","description":"Assets already attached to the campaign in Google Ads. Present only when you send `campaign_id`.","example":[]}},"type":"object","title":"GeneratedAssetsList","description":"The app's generated Google Ads creative."},"GitHubConnectionStatus":{"type":"string","enum":["active","disconnected","error","revoked"],"title":"GitHubConnectionStatus","description":"Health of an app's GitHub repository connection.\n\n- `active` works normally.\n- `disconnected` was disconnected by a user.\n- `error` hit a technical failure.\n- `revoked` means the GitHub App access was withdrawn."},"GitHubOrganization":{"properties":{"id":{"type":"integer","title":"Id","description":"GitHub's numeric ID for the account.","example":1024547},"login":{"type":"string","title":"Login","description":"GitHub username or organization name. Pass it as `org_name` to [Connect a GitHub repository](/api-reference/connect-a-github-repository).","example":"base44"},"avatar_url":{"type":"string","title":"Avatar Url","description":"URL of the account's GitHub profile picture.","example":"https://avatars.githubusercontent.com/u/1024547?v=4"},"installation_id":{"type":"string","title":"Installation Id","description":"ID of the Base44 GitHub App installation on this account. Pass it as `installation_id` to [Connect a GitHub repository](/api-reference/connect-a-github-repository).","example":"58231904"},"type":{"type":"string","enum":["User","Organization"],"title":"Type","description":"Either `User` for your personal account or `Organization` for an organization.","example":"Organization"},"repository_selection":{"type":"string","enum":["all","selected"],"title":"Repository Selection","description":"Whether the Base44 GitHub App can reach all repositories in this account or only chosen ones. Either `all` or `selected`. You can't create a new repository under an account set to `selected`, so switch that account to all repositories in GitHub first, under Settings > Applications > Base44 > Repository access.","default":"all","example":"all"}},"type":"object","required":["id","login","avatar_url","installation_id","type"],"title":"GitHubOrganization","description":"A GitHub account, personal or organization, with the Base44 GitHub App installed."},"GitHubPullResult":{"properties":{"synced":{"type":"boolean","title":"Synced","description":"Whether this call applied new commits to the app.","example":true},"already_up_to_date":{"type":"boolean","title":"Already Up To Date","description":"Whether the repository's head was already the last commit Base44 pulled, so there was nothing to do.","example":false},"commits_pulled":{"type":"integer","title":"Commits Pulled","description":"How many commits `commits` holds, so commits Base44 pushed itself aren't counted. The value is `0` when nothing was pulled.","example":2},"latest_commit_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Latest Commit Hash","description":"Repository head after the pull, or `null` when nothing was pulled.","example":"4c7e1f90ab3d5628e1a0f7b24c9d8e6350a1b2c4"},"commits":{"items":{"$ref":"#/components/schemas/GitHubPulledCommit"},"type":"array","title":"Commits","description":"The commits this call applied, oldest first. Commits Base44 pushed itself are left out, so a pull of only those comes back with an empty list.","example":[{"author_email":"dana@example.com","author_name":"Dana Levi","message":"Fix the lead scoring rounding","sha":"4c7e1f90ab3d5628e1a0f7b24c9d8e6350a1b2c4","short_sha":"4c7e1f9","timestamp":"2026-08-15T09:08:41+00:00","url":"https://github.com/base44/lead-tracker/commit/4c7e1f90ab3d5628e1a0f7b24c9d8e6350a1b2c4"}]},"files_summary":{"anyOf":[{"$ref":"#/components/schemas/GitHubPulledFiles"},{"type":"null"}],"description":"Change counts for the pulled commits, or `null` when nothing was pulled."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why the pull didn't happen, or `null` when it succeeded. One of `not_connected`, `no_installation`, `sync_in_progress`, `connection_error`, `merge_conflict`, `sandbox_sync_failed`, `rate_limit_exceeded`, `rate_limit_low`, `github_api_error` or `unexpected_error`.","example":"merge_conflict"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message","description":"Human-readable explanation of `error`, or `null` when the pull succeeded.","example":"Merge conflict while applying the pulled commits"},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Ms","description":"How long the pull took, in milliseconds, or `null` when Base44 recorded no duration for it.","example":4120}},"type":"object","required":["synced","already_up_to_date","commits_pulled"],"title":"GitHubPullResult","description":"Outcome of a pull from the connected repository."},"GitHubPulledCommit":{"properties":{"sha":{"type":"string","title":"Sha","description":"Full commit SHA.","example":"4c7e1f90ab3d5628e1a0f7b24c9d8e6350a1b2c4"},"short_sha":{"type":"string","title":"Short Sha","description":"Short form of the commit SHA.","example":"4c7e1f9"},"message":{"type":"string","title":"Message","description":"Commit message.","example":"Fix the lead scoring rounding"},"author_name":{"type":"string","title":"Author Name","description":"The author's GitHub username when GitHub reports one, otherwise the name recorded in the commit.","example":"dana-levi"},"author_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author Email","description":"Email recorded as the commit's author, or `null` when GitHub doesn't report one.","example":"dana@example.com"},"timestamp":{"type":"string","title":"Timestamp","description":"When the commit was authored, in ISO 8601.","example":"2026-08-15T09:08:41+00:00"},"url":{"type":"string","title":"Url","description":"URL of the commit on GitHub.","example":"https://github.com/base44/lead-tracker/commit/4c7e1f90ab3d5628e1a0f7b24c9d8e6350a1b2c4"}},"type":"object","required":["sha","short_sha","message","author_name","timestamp","url"],"title":"GitHubPulledCommit","description":"One commit brought in by a pull."},"GitHubPulledFiles":{"properties":{"total_files":{"type":"integer","title":"Total Files","description":"Files changed across the pulled commits.","example":7},"files_with_content":{"type":"integer","title":"Files With Content","description":"Changed files whose new content Base44 applied to the app.","example":6},"files_metadata_only":{"type":"integer","title":"Files Metadata Only","description":"Changed files Base44 recorded without their content, because GitHub didn't return a usable diff for them.","example":1},"files_deleted":{"type":"integer","title":"Files Deleted","description":"Files removed from the app by the pull.","example":1},"total_additions":{"type":"integer","title":"Total Additions","description":"Lines added across the pulled commits.","example":214},"total_deletions":{"type":"integer","title":"Total Deletions","description":"Lines removed across the pulled commits.","example":31}},"type":"object","required":["total_files","files_with_content","files_metadata_only","files_deleted","total_additions","total_deletions"],"title":"GitHubPulledFiles","description":"How much the pulled commits changed."},"GitHubPushResult":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the push succeeded.","example":true},"commit_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Commit Hash","description":"Commit pushed to the repository, or `null` when nothing was pushed.","example":"4c7e1f90ab3d5628e1a0f7b24c9d8e6350a1b2c4"},"commits_pushed":{"type":"boolean","title":"Commits Pushed","description":"Whether the push moved the repository forward. The value is `false` both when the repository already had this commit and when nothing was pushed at all.","example":true},"skipped":{"type":"boolean","title":"Skipped","description":"Whether the push was skipped. This is `true` for exactly one reason, which is that the connection carries no GitHub App installation. Every other reason nothing was pushed comes back `false`, including no connection at all and a connection in `error` or `revoked`, so read `error` rather than this flag.","example":false},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why the push didn't happen, or `null` when it succeeded. Plain text, not a code you can branch on.","example":"App not connected to GitHub"},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Ms","description":"How long the push took, in milliseconds, or `null` when Base44 rejected the request before starting one.","example":2860}},"type":"object","required":["success","commits_pushed","skipped"],"title":"GitHubPushResult","description":"Outcome of a push to the connected repository."},"GithubOrgPolicyStatus":{"properties":{"restricted":{"type":"boolean","title":"Restricted","description":"Whether the app's workspace limits GitHub repositories to an approved list of organizations.","example":false}},"type":"object","required":["restricted"],"title":"GithubOrgPolicyStatus","description":"Whether the app's workspace limits which GitHub organizations it can use."},"GoogleAdsAccountResource":{"properties":{"id":{"type":"string","title":"Id","description":"Base44's ID for the account. Pass it as `account_id` on the other account endpoints.","example":"68b1c0d4e7b91d003c45a1f8"},"account_name":{"type":"string","title":"Account Name","description":"Name of the Google Ads account.","example":"Nordwind Furniture"},"google_customer_id":{"type":"string","title":"Google Customer Id","description":"The account's customer ID in Google Ads. Empty until Google has provisioned it.","example":"1234567890"},"status":{"type":"string","title":"Status","description":"State of the account. Either `ACTIVE`, `BLOCKED`, `SUSPENDED`, `BILLING_HOLD`, `CANCELED`, `PENDING_VERIFICATION`, `PENDING_BILLING_SETUP`, or `PENDING_CLOSE`. Only an `ACTIVE` account can create or resume campaigns.","example":"ACTIVE"},"currency_code":{"type":"string","title":"Currency Code","description":"Currency the account bills in, as an ISO 4217 code.","example":"EUR"},"timezone":{"type":"string","title":"Timezone","description":"Time zone the account reports in, as an IANA name.","example":"Europe/Berlin"},"budget_active":{"type":"boolean","title":"Budget Active","description":"Whether the account has a live Google Ads budget, which campaigns need in order to serve.","example":true},"billing_cadence":{"type":"string","title":"Billing Cadence","description":"How often the account is charged: `1w`, `2w`, or `1m`. Only drives charging when `charge_trigger_mode` is `cadence`.","example":"1w"},"charge_trigger_mode":{"type":"string","title":"Charge Trigger Mode","description":"What triggers a charge on this account. A mode of `cadence` charges on the `billing_cadence` schedule, while `threshold` is a legacy mode that charges when spend crosses a ladder of thresholds and ignores `billing_cadence` entirely.","example":"cadence"},"monthly_spend_cap_micros":{"type":"integer","title":"Monthly Spend Cap Micros","description":"Monthly spend cap in micros of the account currency, so `500000000` is 500.00. The value is `0` when no cap is set.","example":500000000},"auto_renew":{"type":"boolean","title":"Auto Renew","description":"Whether the account's budget renews automatically at the end of its window.","example":true},"budget_window_end":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Budget Window End","description":"When the current budget window ends, or `null` before a budget exists.","example":"2026-09-30T23:59:59Z"},"terms_accepted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Terms Accepted At","description":"When the Google Ads terms were accepted for this account, or `null` until they are.","example":"2026-08-25T11:20:00Z"},"business_profile_location_id":{"type":"string","title":"Business Profile Location Id","description":"Google Business Profile location linked to the account, as `locations/<id>`. Empty when none is linked.","example":"locations/12345"},"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"When the account was created in Base44.","example":"2026-08-25T11:20:00Z"},"updated_date":{"type":"string","format":"date-time","title":"Updated Date","description":"When Base44 last changed its record of the account.","example":"2026-08-25T14:05:00Z"}},"type":"object","required":["id","account_name","google_customer_id","status","currency_code","timezone","budget_active","billing_cadence","charge_trigger_mode","monthly_spend_cap_micros","auto_renew","business_profile_location_id","created_date","updated_date"],"title":"GoogleAdsAccountResource","description":"The account fields this API commits to."},"GoogleAdsAdCopy":{"properties":{"headlines":{"items":{"type":"string"},"type":"array","title":"Headlines","description":"Suggested headlines, each within Google's 30-character limit.","example":["Handmade Oak Tables","Free Statewide Delivery","Built To Order"]},"descriptions":{"items":{"type":"string"},"type":"array","title":"Descriptions","description":"Suggested description lines, each within Google's 90-character limit.","example":["Solid oak dining tables built to order in San Francisco. Free delivery."]},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"Two-letter code for the language the copy came back in, or `null` when the model did not report one. Pass it as `settings.language_code` on [Create campaign](/api-reference/create-google-ads-campaign) so the campaign's language matches its copy.","example":"en"},"source":{"type":"string","title":"Source","description":"`ai` when a language model wrote this copy. Any other value means it did not, and you are looking at fallback text built from the business details you sent.","example":"ai"}},"type":"object","required":["headlines","descriptions","source"],"title":"GoogleAdsAdCopy","description":"Headlines and descriptions for a campaign's ads."},"GoogleAdsAppliedBudgetRecommendation":{"properties":{"id":{"type":"string","title":"Id","description":"Base44's ID for the campaign whose budget changed.","example":"68b1c0d4e7b91d003c45a1f2"},"daily_budget_micros":{"type":"integer","title":"Daily Budget Micros","description":"The campaign's daily budget after the change, in micros.","example":45000000},"already_applied":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Already Applied","description":"`true` when this recommendation had already been applied and nothing changed on this call, so a retry is safe. Absent when the call applied the change.","example":true}},"type":"object","required":["id","daily_budget_micros"],"title":"GoogleAdsAppliedBudgetRecommendation","description":"The campaign's budget after applying Google's recommendation."},"GoogleAdsBudgetBoundaries":{"properties":{"currency_code":{"type":"string","title":"Currency Code","description":"The currency these amounts are in, upper-cased from what you sent.","example":"USD"},"campaign_type":{"type":"string","title":"Campaign Type","description":"The campaign type these bounds apply to, upper-cased from what you sent.","example":"SMART"},"min_daily_budget_micros":{"type":"integer","title":"Min Daily Budget Micros","description":"Smallest daily budget Google accepts for this campaign type and currency, in micros (1,000,000 micros = 1 unit of the currency). Sending less than this to [Create campaign](/api-reference/create-google-ads-campaign) is rejected with a 400.","example":10000000},"max_daily_budget_micros":{"type":"integer","title":"Max Daily Budget Micros","description":"Largest daily budget accepted for this campaign type and currency, in micros.","example":1000000000},"recommended_daily_budget_micros":{"type":"integer","title":"Recommended Daily Budget Micros","description":"A sensible starting daily budget, in micros. Never above `max_daily_budget_micros`.","example":30000000},"preset_daily_budget_micros":{"items":{"type":"integer"},"type":"array","title":"Preset Daily Budget Micros","description":"Three suggested daily budgets, in micros, from lowest to highest. These are raw presets, so unlike `recommended_daily_budget_micros` the highest one can sit above `max_daily_budget_micros`.","example":[15000000,30000000,60000000]},"usd_to_currency_rate":{"type":"number","title":"Usd To Currency Rate","description":"How many units of this currency Base44 counts per US dollar when it converts the presets.","example":1.0}},"type":"object","required":["currency_code","campaign_type","min_daily_budget_micros","max_daily_budget_micros","recommended_daily_budget_micros","preset_daily_budget_micros","usd_to_currency_rate"],"title":"GoogleAdsBudgetBoundaries","description":"The daily budget range Google accepts for a campaign type and currency."},"GoogleAdsBudgetEstimate":{"properties":{"estimated_daily_clicks_low":{"type":"integer","title":"Estimated Daily Clicks Low","description":"Low end of Google's forecast daily clicks.","example":12},"estimated_daily_clicks_high":{"type":"integer","title":"Estimated Daily Clicks High","description":"High end of Google's forecast daily clicks.","example":31},"estimated_monthly_cost":{"type":"integer","title":"Estimated Monthly Cost","description":"Forecast spend over 30 days, in micros (1,000,000 micros = 1 unit of the account's currency). This is the recommended daily budget times 30, so it is a 30-day figure rather than a calendar month, and it can differ from the `daily_budget` you sent.","example":900000000}},"type":"object","required":["estimated_daily_clicks_low","estimated_daily_clicks_high","estimated_monthly_cost"],"title":"GoogleAdsBudgetEstimate","description":"Google's forecast for a set of keyword themes at a daily budget."},"GoogleAdsCampaignMetrics":{"properties":{"impressions":{"type":"integer","title":"Impressions","description":"Times the campaign's ads were shown.","example":4821},"clicks":{"type":"integer","title":"Clicks","description":"Clicks the campaign received.","example":137},"cost_micros":{"type":"integer","title":"Cost Micros","description":"Spend over the window, in micros (1,000,000 micros = 1 unit of the account's currency).","example":48200000},"conversions":{"type":"number","title":"Conversions","description":"Conversions Google attributed to the campaign.","example":6.0},"ctr":{"type":"number","title":"Ctr","description":"Click-through rate as a fraction, so `0.028` is 2.8%.","example":0.028}},"type":"object","required":["impressions","clicks","cost_micros","conversions","ctr"],"title":"GoogleAdsCampaignMetrics","description":"Performance figures for one campaign over the requested window."},"GoogleAdsCampaignMetricsResponse":{"properties":{"metrics":{"additionalProperties":{"$ref":"#/components/schemas/GoogleAdsCampaignMetrics"},"type":"object","title":"Metrics","description":"One entry per campaign, keyed by the Google Ads campaign ID. This is the `google_campaign_id` field from [List campaigns](/api-reference/list-google-ads-campaigns), not Base44's `id`, so join on that field. Campaigns with no activity in the window are left out entirely.","example":{"21458812345":{"clicks":137,"conversions":6.0,"cost_micros":48200000,"ctr":0.028,"impressions":4821}}},"degraded":{"type":"boolean","title":"Degraded","description":"`true` when Google could not be reached and `metrics` is empty for that reason rather than because nothing ran. Always check this before showing zeros.","example":false}},"type":"object","required":["metrics","degraded"],"title":"GoogleAdsCampaignMetricsResponse","description":"Live metrics for every campaign on the account, keyed by Google's campaign ID."},"GoogleAdsCampaignSuggestion":{"properties":{"id":{"type":"string","title":"Id","description":"Stable ID for the suggestion: `smart-traffic`, `smart-leads` or `pmax-leads`.","example":"smart-traffic"},"title":{"type":"string","title":"Title","description":"One-line name for the approach. Always English, even when the copy in `payload` is in another language.","example":"Smart Search - drive traffic"},"subtitle":{"type":"string","title":"Subtitle","description":"What this approach is best for. Always English, even when the copy in `payload` is in another language.","example":"Best for raising overall visits to the site"},"campaign_type":{"type":"string","title":"Campaign Type","description":"`SMART` or `PERFORMANCE_MAX`.","example":"SMART"},"estimated_daily_clicks":{"type":"integer","title":"Estimated Daily Clicks","description":"Rough daily clicks this approach could produce at the budget you sent. At least 1.","example":24},"estimate_source":{"type":"string","title":"Estimate Source","description":"Where `estimated_daily_clicks` came from. `planner_anchored` means it is scaled from Google's own forecast for this account. `heuristic` means Google was not consulted, because the app has no connected account yet or the forecast was unavailable, and the number is a rough calculation from the budget alone.","example":"planner_anchored"},"payload":{"$ref":"#/components/schemas/GoogleAdsCampaignSuggestionDraft","description":"The campaign body to send to [Create campaign](/api-reference/create-google-ads-campaign) to launch this suggestion."}},"type":"object","required":["id","title","subtitle","campaign_type","estimated_daily_clicks","estimate_source","payload"],"title":"GoogleAdsCampaignSuggestion","description":"One suggested campaign the caller can preview and launch."},"GoogleAdsCampaignSuggestionDraft":{"properties":{"campaign_name":{"type":"string","title":"Campaign Name","description":"Suggested campaign name, trimmed to 120 characters.","example":"Nordwind Furniture - Traffic"},"campaign_type":{"type":"string","title":"Campaign Type","description":"`SMART` or `PERFORMANCE_MAX`.","example":"SMART"},"business_name":{"type":"string","title":"Business Name","description":"The business name you sent, echoed back.","example":"Nordwind Furniture"},"landing_page":{"type":"string","title":"Landing Page","description":"The `landing_page_url` you sent, echoed back.","example":"https://nordwind-furniture.com"},"daily_budget_micros":{"type":"integer","title":"Daily Budget Micros","description":"The `daily_budget_micros` you sent, echoed back.","example":30000000},"headlines":{"items":{"type":"string"},"type":"array","title":"Headlines","description":"Generated headlines for the campaign's ads.","example":["Handmade Oak Tables","Built To Order"]},"descriptions":{"items":{"type":"string"},"type":"array","title":"Descriptions","description":"Generated description lines for the campaign's ads.","example":["Solid oak dining tables built to order in San Francisco."]},"keyword_themes":{"items":{"type":"string"},"type":"array","title":"Keyword Themes","description":"Up to five generated keyword themes.","example":["handmade oak furniture","custom dining tables"]},"settings":{"additionalProperties":{"type":"string"},"type":"object","title":"Settings","description":"Campaign settings to send on unchanged. Carries `language_code`, the language this suggestion's copy was written in, whenever the model reported one; empty otherwise. Drop it and the campaign is created targeting English while its copy is not.","example":{"language_code":"de"}},"logo_url":{"type":"string","title":"Logo Url","description":"Always empty. Performance Max campaigns need a logo to launch, so fill this in before you send the draft.","example":""},"images":{"items":{"type":"string"},"type":"array","title":"Images","description":"Always empty. Performance Max campaigns need marketing images to launch, so fill these in before you send the draft.","example":[]}},"type":"object","required":["campaign_name","campaign_type","business_name","landing_page","daily_budget_micros","headlines","descriptions","keyword_themes","settings","logo_url","images"],"title":"GoogleAdsCampaignSuggestionDraft","description":"A ready-to-send body for [Create campaign](/api-reference/create-google-ads-campaign)."},"GoogleAdsCampaignSuggestions":{"properties":{"suggestions":{"items":{"$ref":"#/components/schemas/GoogleAdsCampaignSuggestion"},"type":"array","title":"Suggestions","description":"The suggested campaigns, most general first. At most three, however many you asked for.","example":[{"campaign_type":"SMART","estimate_source":"heuristic","estimated_daily_clicks":24,"id":"smart-traffic","payload":{"business_name":"Nordwind Furniture","campaign_name":"Nordwind Furniture - Traffic","campaign_type":"SMART","daily_budget_micros":30000000,"descriptions":["Solid oak dining tables built to order."],"headlines":["Handmade Oak Tables"],"images":[],"keyword_themes":["handmade oak furniture"],"landing_page":"https://nordwind-furniture.com","logo_url":"","settings":{"language_code":"en"}},"subtitle":"Best for raising overall visits to the site","title":"Smart Search - drive traffic"}]},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"Two-letter code for the language the generated copy came back in, or `null` when the model did not report one.","example":"en"},"source":{"type":"string","title":"Source","description":"`ai` when a language model wrote the copy in these suggestions, `fallback` when it did not run or returned nothing.","example":"ai"}},"type":"object","required":["suggestions","source"],"title":"GoogleAdsCampaignSuggestions","description":"Suggested campaigns for a business description."},"GoogleAdsCampaignTargeting":{"properties":{"keyword_themes":{"items":{"type":"string"},"type":"array","title":"Keyword Themes","description":"The themes the campaign matches on.","example":["handmade oak furniture","custom dining tables"]},"excluded_terms":{"items":{"type":"string"},"type":"array","title":"Excluded Terms","description":"Search terms the campaign is told not to match.","example":["free","repair"]},"targeted_locations":{"items":{"$ref":"#/components/schemas/GoogleAdsTargetedLocation"},"type":"array","title":"Targeted Locations","description":"Locations the campaign targets.","example":[{"name":"San Francisco","resourceName":"geoTargetConstants/1014221"}]},"excluded_locations":{"items":{"$ref":"#/components/schemas/GoogleAdsTargetedLocation"},"type":"array","title":"Excluded Locations","description":"Locations the campaign excludes.","example":[{"name":"Oakland","resourceName":"geoTargetConstants/1014044"}]},"language_code":{"type":"string","title":"Language Code","description":"Two-letter code for the language the campaign advertises in. Defaults to `en` when the campaign has none set.","example":"en"}},"type":"object","required":["keyword_themes","excluded_terms","targeted_locations","excluded_locations","language_code"],"title":"GoogleAdsCampaignTargeting","description":"What a campaign currently targets, read live from Google."},"GoogleAdsEnhancedPrompt":{"properties":{"enhanced_prompt":{"type":"string","title":"Enhanced Prompt","description":"The rewritten prompt. Empty when you sent an empty or whitespace-only `raw_prompt`, in which case no model runs and no quota is spent.","example":"A warm, sunlit photograph of a handmade oak dining table in a bright modern kitchen, shallow depth of field, lifestyle advertising photography"},"original_prompt":{"type":"string","title":"Original Prompt","description":"The `raw_prompt` you sent, echoed back unchanged.","example":"oak table photo"}},"type":"object","required":["enhanced_prompt","original_prompt"],"title":"GoogleAdsEnhancedPrompt","description":"A rewritten image-generation prompt."},"GoogleAdsGeoTarget":{"properties":{"resource_name":{"type":"string","title":"Resource Name","description":"Google's identifier for the location. Pass it in `geo_targets` when you create a campaign.","example":"geoTargetConstants/1014221"},"name":{"type":"string","title":"Name","description":"The location's own name.","example":"San Francisco"},"canonical_name":{"type":"string","title":"Canonical Name","description":"The location's full path, including its region and country.","example":"San Francisco,California,United States"},"country_code":{"type":"string","title":"Country Code","description":"ISO 3166-1 alpha-2 country code the location sits in.","example":"US"},"target_type":{"type":"string","title":"Target Type","description":"What kind of place this is, for example `City`, `Region` or `Country`.","example":"City"},"reach":{"type":"integer","title":"Reach","description":"Google's estimate of how many people can be reached in this location.","example":4200000},"search_term":{"type":"string","title":"Search Term","description":"The `query` value this result was matched against, echoed back.","example":"san francisco"}},"type":"object","required":["resource_name","name","canonical_name","country_code","target_type","reach","search_term"],"title":"GoogleAdsGeoTarget","description":"One location Google matched for the search string."},"GoogleAdsHeroPreview":{"properties":{"search_query":{"type":"string","title":"Search Query","description":"A search someone might type to find this business.","example":"oak dining table san francisco"},"headline":{"type":"string","title":"Headline","description":"Headline of the sample ad.","example":"Handmade Oak Dining Tables"},"description":{"type":"string","title":"Description","description":"Body text of the sample ad.","example":"Built to order in our San Francisco workshop. Free delivery statewide."},"subtitle":{"type":"string","title":"Subtitle","description":"The display line shown under the headline, usually the site.","example":"nordwind-furniture.com"},"business_name":{"type":"string","title":"Business Name","description":"The business name this preview was written for, taken from the app.","example":"Nordwind Furniture"},"source":{"type":"string","title":"Source","description":"`ai` when a language model wrote this preview, `fallback` when it did not run or returned nothing and Base44 filled in generic copy instead.","example":"ai"}},"type":"object","required":["search_query","headline","description","subtitle","business_name","source"],"title":"GoogleAdsHeroPreview","description":"A sample search result to show before the app has a Google Ads account."},"GoogleAdsInvoiceSummary":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the invoice.","example":"68b1c0d4e7b91d003c45a1f6"},"invoice_date":{"type":"string","title":"Invoice Date","description":"Date the invoice was issued, as `YYYY-MM-DD`.","example":"2026-08-25"},"period_start":{"type":"string","title":"Period Start","description":"First day the invoice covers, as `YYYY-MM-DD`.","example":"2026-08-18"},"period_end":{"type":"string","title":"Period End","description":"Last day the invoice covers, as `YYYY-MM-DD`.","example":"2026-08-24"},"status":{"type":"string","title":"Status","description":"State of the invoice. Either `PENDING`, `PAID`, `FAILED`, or `VOID`.","example":"PAID"},"currency_code":{"type":"string","title":"Currency Code","description":"Currency of every amount on the invoice, as an ISO 4217 code.","example":"EUR"},"ad_spend_micros":{"type":"integer","title":"Ad Spend Micros","description":"Ad spend for the period in micros, so `248000000` is 248.00.","example":248000000},"service_fee_micros":{"type":"integer","title":"Service Fee Micros","description":"Base44's service fee for the period, in micros.","example":24800000},"tax_micros":{"type":"integer","title":"Tax Micros","description":"Tax charged on the invoice, in micros.","example":51870000},"total_micros":{"type":"integer","title":"Total Micros","description":"Size of the invoice in micros, always positive. On a refund this is the magnitude refunded rather than an amount charged, so read `signed_total_micros` or `is_refund` before treating it as money owed.","example":324670000},"is_refund":{"type":"boolean","title":"Is Refund","description":"Whether the row is a refund rather than a charge.","example":false},"signed_total_micros":{"type":"integer","title":"Signed Total Micros","description":"The invoice total with its direction applied: positive on a charge, negative on a refund. Sum this rather than `total_micros` when you total an account's billing.","example":324670000},"billing_type":{"type":"string","title":"Billing Type","description":"Why the invoice was raised: `weekly` on the billing cycle, `threshold` when spend crossed the cap, `reconciliation` to true up an earlier period.","example":"weekly"}},"type":"object","required":["id","invoice_date","period_start","period_end","status","currency_code","ad_spend_micros","service_fee_micros","tax_micros","total_micros","is_refund","signed_total_micros","billing_type"],"title":"GoogleAdsInvoiceSummary","description":"One billing invoice for the app's Google Ads account."},"GoogleAdsKeywordThemeSuggestions":{"properties":{"themes":{"items":{"type":"string"},"type":"array","title":"Themes","description":"Suggested themes for the campaign. Can be empty.","example":["handmade oak furniture","custom dining tables","solid wood furniture"]},"source":{"type":"string","title":"Source","description":"Where the themes came from. `google` means Google's own suggester. `generated` means a language model wrote them, which only happens for apps with that feature enabled. An empty `themes` list is always reported as `google`, including when the AI quota was spent or the campaign had too little detail to ask Google, so `google` with no themes does not mean Google returned nothing.","example":"google"}},"type":"object","required":["themes","source"],"title":"GoogleAdsKeywordThemeSuggestions","description":"Suggested keyword themes for a campaign, and where they came from."},"GoogleAdsSearchThemes":{"properties":{"search_themes":{"items":{"type":"string"},"type":"array","title":"Search Themes","description":"Up to 10 themes, each at most 80 characters. Pass them as `search_themes` when you create a Performance Max campaign. An empty list means the model returned nothing usable.","example":["handmade oak furniture","custom dining tables","san francisco woodworking"]}},"type":"object","required":["search_themes"],"title":"GoogleAdsSearchThemes","description":"Short themes describing what a Performance Max campaign should match."},"GoogleAdsTargetedLocation":{"properties":{"resourceName":{"type":"string","title":"Resourcename","description":"Google's identifier for the location, as returned by [Search locations](/api-reference/search-google-ads-locations). Note this field is camel-cased, unlike the rest of this API.","example":"geoTargetConstants/1014221"},"name":{"type":"string","title":"Name","description":"The location's display name. Falls back to the numeric ID from `resourceName` when Base44 has no stored label for it, which happens for locations set before the label was recorded.","example":"San Francisco"}},"type":"object","required":["resourceName","name"],"title":"GoogleAdsTargetedLocation","description":"One location a campaign targets or excludes."},"GoogleAdsTextAssets":{"properties":{"HEADLINE":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Headline","description":"Short headlines, each within Google's 30-character limit. Present only when you asked for `HEADLINE`.","example":["Handmade Oak Tables","Built To Order"]},"LONG_HEADLINE":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Long Headline","description":"Long headlines, each within Google's 90-character limit. Present only when you asked for `LONG_HEADLINE`.","example":["Solid oak dining tables, built to order in San Francisco"]},"DESCRIPTION":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Description","description":"Description lines, each within Google's 90-character limit. Present only when you asked for `DESCRIPTION`.","example":["Free statewide delivery on every table we build."]},"BUSINESS_NAME":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Name","description":"A single business name, trimmed to Google's 25-character limit. This one is a string rather than an array, unlike every other asset type here. Present only when you asked for `BUSINESS_NAME`.","example":"Nordwind Furniture"},"CALL_TO_ACTION_SELECTION":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Call To Action Selection","description":"Call-to-action values Google accepts. This is a fixed list, not a generated one. Present only when you asked for `CALL_TO_ACTION_SELECTION`.","example":["LEARN_MORE","SHOP_NOW","SIGN_UP","CONTACT_US"]}},"type":"object","title":"GoogleAdsTextAssets","description":"Suggested text for each asset type you asked for."},"GroupedAggregationRequest":{"properties":{"event_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Name","description":"Name of the event to aggregate. Omit to aggregate every event the app tracks.","example":"checkout_completed"},"start_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start Time","description":"Start of the time range, as a UTC timestamp in ISO 8601 format. Defaults to 30 days ago.","example":"2026-07-03T00:00:00Z"},"end_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End Time","description":"End of the time range, as a UTC timestamp in ISO 8601 format. Defaults to now.","example":"2026-08-02T00:00:00Z"},"max_groups":{"type":"integer","maximum":1000.0,"minimum":1.0,"title":"Max Groups","description":"Maximum number of groups to return per metric, between 1 and 1000. The largest groups are kept.","default":100,"example":10},"metrics":{"items":{"$ref":"#/components/schemas/AggregationMetric"},"type":"array","maxItems":10,"minItems":1,"title":"Metrics","description":"Between 1 and 10 metrics to compute. Each one must set `field`, which is the field it groups by. Defaults to an event count per event name, named `event_count`.","example":[{"field":"page_url","function":"count","name":"event_count"}]}},"type":"object","title":"GroupedAggregationRequest","description":"Request for grouped event aggregation."},"GroupedAggregationResponse":{"properties":{"event_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Name","description":"The event name that was aggregated, or `null` when the request covered every event.","example":"checkout_completed"},"group_count":{"type":"integer","title":"Group Count","description":"Number of entries in `groups`, summed across every metric.","default":0,"example":10},"totals":{"additionalProperties":{"anyOf":[{"type":"number"},{"type":"integer"}]},"type":"object","title":"Totals","description":"Each metric computed over the whole range without grouping, keyed by metric name.","example":{"event_count":3840}},"groups":{"items":{"$ref":"#/components/schemas/AggregationGroup"},"type":"array","title":"Groups","description":"One entry per group value per metric, largest metric value first within each metric.","example":[{"group":"/checkout","metrics":{"event_count":842}},{"group":"/pricing","metrics":{"event_count":517}}]}},"type":"object","title":"GroupedAggregationResponse","description":"Analytics events aggregated by the value of a field."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ImportBranchRequest":{"properties":{"branch_name":{"type":"string","maxLength":255,"minLength":1,"title":"Branch Name","description":"Name of the branch to import, as it exists in the connected repository. Can't be the repository's default branch.","example":"feature/reporting"}},"type":"object","required":["branch_name"],"title":"ImportBranchRequest","description":"Request to import a branch that already exists in the connected repository."},"ImportedBranchSummary":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the new Base44 branch.","example":"68b1c0d4e7b91d003c45a1f7"},"branch_name":{"type":"string","title":"Branch Name","description":"The GitHub branch name, stored exactly as you sent it. Base44's own commits go to this same branch.","example":"feature/reporting"},"conversation_id":{"type":"string","title":"Conversation Id","description":"ID of the branch's own builder conversation. The branch's chat history is separate from the app's main chat.","example":"68b1c0d4e7b91d003c45a1f8"},"base_git_commit_hash":{"type":"string","title":"Base Git Commit Hash","description":"Commit where the branch forked off the repository's default branch.","example":"9f2c1ab7d4e58036bb1f4c0a7de92b41c0d5e8a3"},"last_git_commit_hash":{"type":"string","title":"Last Git Commit Hash","description":"Head commit of the GitHub branch at the moment it was imported.","example":"4c7e1f90ab3d5628e1a0f7b24c9d8e6350a1b2c4"},"created_date":{"type":"string","title":"Created Date","description":"When the branch was created, in `YYYY-MM-DDTHH:MM:SS.ffffffZ` format (UTC).","example":"2026-08-15T09:10:00.123456Z"}},"type":"object","required":["id","branch_name","conversation_id","base_git_commit_hash","last_git_commit_hash","created_date"],"title":"ImportedBranchSummary","description":"The Base44 branch created from an existing GitHub branch."},"InsightDismissResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Always `true`. Dismissing an already-dismissed suggestion succeeds the same way.","example":true}},"type":"object","required":["success"],"title":"InsightDismissResponse","description":"Confirmation that the suggestion is hidden."},"LaunchAccountSummary":{"properties":{"id":{"type":"string","title":"Id","description":"Base44's ID for the account.","example":"68b1c0d4e7b91d003c45a1f0"},"status":{"type":"string","title":"Status","description":"Where the account stands: `ACTIVE` once it can spend, `PENDING_BILLING_SETUP` while Base44 is still wiring its billing.","example":"ACTIVE"},"account_name":{"type":"string","title":"Account Name","description":"Name Base44 gave the account on Google, taken from the business details.","example":"Nordwind Furniture"},"currency_code":{"type":"string","title":"Currency Code","description":"Currency the account is billed in. Fixed when the account is created and never changes.","example":"EUR"},"timezone":{"type":"string","title":"Timezone","description":"Time zone the account reports its days in. Also fixed at creation.","example":"Europe/Berlin"},"google_customer_id":{"type":"string","title":"Google Customer Id","description":"The account's Google Ads customer ID. Empty for the moment between Base44 reserving the account and Google returning its ID.","example":"1234567890"}},"type":"object","required":["id","status","account_name","currency_code","timezone","google_customer_id"],"title":"LaunchAccountSummary","description":"The Google Ads account the campaign now lives on."},"LaunchBusinessInfo":{"properties":{"name":{"type":"string","title":"Name","description":"Business name. Base44 falls back to the app's name when this is empty.","example":"Nordwind Furniture"},"category":{"type":"string","title":"Category","description":"Business category, empty when none is saved.","example":"Furniture store"},"description":{"type":"string","title":"Description","description":"What the business does, empty when none is saved.","example":"Handmade oak furniture, delivered across Germany."},"phone":{"type":"string","title":"Phone","description":"Contact phone number, empty when none is saved.","example":"+493012345678"}},"type":"object","required":["name","category","description","phone"],"title":"LaunchBusinessInfo","description":"The business details Base44 will put on the Google Ads account."},"LaunchReadinessResponse":{"properties":{"has_payment_method":{"type":"boolean","title":"Has Payment Method","description":"Whether the workspace has a card on file. Launching without one is rejected with a 402.","example":true},"stripe_unavailable":{"type":"boolean","title":"Stripe Unavailable","description":"`true` when Base44 could not reach its payment provider, so `has_payment_method` is not trustworthy. Launching in this state is rejected rather than allowed through.","example":false},"currency_code":{"type":"string","title":"Currency Code","description":"Currency the campaign will be billed in. On an account that already exists this is the currency it was created with and cannot change. Otherwise Base44 derives it from where the request comes from, not from how the workspace pays.","example":"EUR"},"account_exists":{"type":"boolean","title":"Account Exists","description":"Whether the app already has a Google Ads account. When `false`, launching creates one, which cannot be undone.","example":false},"account_id":{"type":"string","title":"Account Id","description":"ID of that account, empty when there is none yet.","example":"68b1c0d4e7b91d003c45a1f0"},"landing_page_ready":{"type":"boolean","title":"Landing Page Ready","description":"Whether Base44 resolved a page Google can actually crawl. Launching without one gets the campaign rejected by Google after the account already exists.","example":true},"landing_page":{"type":"string","title":"Landing Page","description":"The exact URL to send as the campaign's `landing_page`: a live custom domain if the app has one, otherwise its published address. Empty when neither resolves. Send this rather than building the URL yourself.","example":"https://example.com"},"business_info":{"anyOf":[{"$ref":"#/components/schemas/LaunchBusinessInfo"},{"type":"null"}],"description":"The business details that will go on the Google Ads account. `null` when the app has none saved, in which case Base44 uses the app's name."}},"type":"object","required":["has_payment_method","stripe_unavailable","currency_code","account_exists","account_id","landing_page_ready","landing_page"],"title":"LaunchReadinessResponse","description":"Everything that decides whether a launch will succeed.\n\nOmits `payment_method_last4` and `payment_method_brand`, which the response\nalso carries. Card detail is not something this endpoint needs to publish to\nanswer \"can this launch\"; `has_payment_method` is. Both fields keep being\nsent, so leaving them undocumented changes no API."},"LaunchResponse":{"properties":{"account":{"$ref":"#/components/schemas/LaunchAccountSummary","description":"The Google Ads account the campaign was created on, whether it already existed or was created by this call."},"campaign":{"$ref":"#/components/schemas/CampaignResource","description":"The campaign that is now live."}},"type":"object","required":["account","campaign"],"title":"LaunchResponse","description":"The account and the campaign, after both exist."},"LibraryAssetSummary":{"properties":{"id":{"type":"string","title":"Id","description":"Base44's ID for the library row.","example":"68b1c0d4e7b91d003c45a1fb"},"session_asset_id":{"type":"string","title":"Session Asset Id","description":"ID the asset was generated under. Pass it as `session_asset_id` to [Accept a generated Google Ads asset](/api-reference/accept-a-generated-google-ads-asset), which works from the library long after the generation session has expired.","example":"b7f3a1c8-52d4-4a0e-9b31-2c6f0d8e4a19"},"session_id":{"type":"string","title":"Session Id","description":"The generation session the asset came from. Empty on a row written before Base44 recorded it.","example":"9c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f"},"asset_field_type":{"type":"string","title":"Asset Field Type","description":"Which slot on the ad this asset fills. Text assets are `HEADLINE`, `LONG_HEADLINE` or `DESCRIPTION`. Image assets are `MARKETING_IMAGE`, `SQUARE_MARKETING_IMAGE`, `PORTRAIT_MARKETING_IMAGE` or `TALL_PORTRAIT_MARKETING_IMAGE`.","example":"SQUARE_MARKETING_IMAGE"},"kind":{"type":"string","title":"Kind","description":"Whether the asset is copy (`text`) or a picture (`image`).","example":"image"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text","description":"The generated copy, or `null` on an image asset.","example":"Handmade oak furniture, built to last"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url","description":"URL of the generated picture, or `null` on a text asset. Unlike the session copy this one is durable, which is what makes the library worth reading.","example":"https://storage.base44.com/gads-assets/b7f3a1c8-square.png"},"source":{"type":"string","title":"Source","description":"Which engine wrote it. The value is `google` for Google's own asset generation and `inhouse` for the Base44 model that covers languages Google does not generate for, and that stands in when Google's call fails.","example":"inhouse"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"Language of a text asset as a lowercase two-letter code, or `null` on an image, because image assets carry no copy.","example":"de"},"channel_type":{"type":"string","title":"Channel Type","description":"Campaign type the asset was generated for, one of `SEARCH`, `PERFORMANCE_MAX`, `DISPLAY` or `DEMAND_GEN`.","example":"PERFORMANCE_MAX"},"status":{"type":"string","title":"Status","description":"Whether the asset has been pushed to Google Ads. The value is `generated` until you accept it and `accepted` afterwards.","example":"generated"},"accepted_campaign_id":{"type":"string","title":"Accepted Campaign Id","description":"The campaign the asset was accepted onto, as **Google Ads'** campaign ID rather than the Base44 `campaign_id` you sent to accept it. Match it against `google_campaign_id` from [List campaigns](/api-reference/list-google-ads-campaigns), not against `id`. Empty while `status` is `generated`.","example":"21458812345"},"accepted_resource_names":{"items":{"type":"string"},"type":"array","title":"Accepted Resource Names","description":"Google Ads resource names created when the asset was accepted. Empty while `status` is `generated`, and empty on an accept where Google returned no resource name.","example":["customers/1234567890/assets/98765432"]},"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"When the asset was generated.","example":"2026-08-25T14:05:00Z"},"updated_date":{"type":"string","format":"date-time","title":"Updated Date","description":"When the row last changed, which is the accept time on an accepted asset.","example":"2026-08-26T09:15:00Z"}},"type":"object","required":["id","session_asset_id","session_id","asset_field_type","kind","text","image_url","source","language","channel_type","status","accepted_campaign_id","accepted_resource_names","created_date","updated_date"],"title":"LibraryAssetSummary","description":"One generated asset in the app's durable library."},"ListEmailDomainsResponse":{"properties":{"email_domains":{"items":{"$ref":"#/components/schemas/EmailDomainInfo"},"type":"array","title":"Email Domains","description":"The app's email domains. Usually one, empty when none are set up, and two while a replacement is in flight.","example":[{"configuration_status":"active","dns_records":[],"domain":"example.com","enabled_at":"2026-08-20T16:31:00","external":true,"from_email":"no-reply@example.com","id":"68b1c0d4e7b91d003c45a1f2","sender_name":"Nordwind Furniture"}]}},"type":"object","required":["email_domains"],"title":"ListEmailDomainsResponse","description":"Response for listing email domains for an app."},"ListEntitySchemasResponse":{"properties":{"schemas":{"items":{"$ref":"#/components/schemas/EntitySchema"},"type":"array","title":"Schemas","description":"The app's entity schemas.","example":[{"entity_name":"Invoice","entity_schema":{"name":"Invoice","properties":{"amount":{"description":"Total amount in cents","type":"number"},"status":{"enum":["draft","sent","paid"],"type":"string"}},"required":["amount"],"rls":{"read":{"created_by":"{{user.email}}"}},"type":"object"}},{"entity_name":"User","entity_schema":{"properties":{"department":{"type":"string"},"employee_id":{"type":"string"}},"type":"object"}}]},"total":{"type":"integer","title":"Total","description":"Number of entity schemas returned.","example":2}},"type":"object","required":["schemas","total"],"title":"ListEntitySchemasResponse","description":"Every entity the app defines, with its schema."},"ManualCheckpointResponse":{"properties":{"created":{"type":"boolean","title":"Created","description":"Whether a checkpoint was saved. `false` means the app has not changed since its most recent checkpoint, so there was nothing to save.","example":true},"checkpoint_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Checkpoint Id","description":"ID of the checkpoint that was saved, or `null` when `created` is `false`. Pass it as `checkpoint_id` to [Deploy an app](/api-reference/deploy-an-app) to publish this version, or as `checkpoint_id` to [Restore checkpoint](/api-reference/restore-checkpoint) to return to it.","example":"6886b8d390dc7e2f4a2c91b3"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Why nothing was saved. `no_changes` is the only value, and it is `null` when `created` is `true`.","example":"no_changes"}},"type":"object","required":["created"],"title":"ManualCheckpointResponse"},"MessageMetadataSummary":{"properties":{"created_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created Date","description":"Time the message was created, as a UTC timestamp in ISO 8601 format.","example":"2026-08-02T14:30:00"},"created_by_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Email","description":"Email of the user whose turn produced the message, or `anonymous` on a message Base44 created with no user in context.","example":"developer@example.com"}},"type":"object","title":"MessageMetadataSummary"},"MessageUsageSummary":{"properties":{"prompt_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Prompt Tokens","description":"Tokens the model read for this message, including the conversation history it was given.","example":18432},"completion_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Completion Tokens","description":"Tokens the model generated for this message.","example":742},"credits_charged":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Credits Charged","description":"Credits charged for this message, or `null` if it was not billed.","example":1.5}},"type":"object","title":"MessageUsageSummary"},"MetricTrendPoint":{"properties":{"date":{"type":"string","title":"Date","description":"First day of the bucket, as `YYYY-MM-DD`. For `WEEKLY` this is the Monday, for `MONTHLY` the first of the month.","example":"2026-08-10"},"value":{"type":"number","title":"Value","description":"The requested metric for the bucket, rounded to two decimals.","example":612.0},"clicks":{"type":"integer","title":"Clicks","description":"Clicks in the bucket, always included so you can chart a second series without another call.","example":612},"conversions":{"type":"number","title":"Conversions","description":"Conversions in the bucket, always included for the same reason.","example":24.0}},"type":"object","required":["date","value","clicks","conversions"],"title":"MetricTrendPoint","description":"One bucket of the requested metric."},"PauseAllCampaignsResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Outcome of the request. A status of `paused` means every campaign paused, and `partial` means at least one did not.","example":"paused"},"paused_count":{"type":"integer","title":"Paused Count","description":"How many campaigns this call paused.","example":3},"total_count":{"type":"integer","title":"Total Count","description":"How many campaigns it tried to pause.","example":3},"failed_count":{"type":"integer","title":"Failed Count","description":"How many could not be paused. Retry the call, or pause those campaigns individually to see why.","example":0}},"type":"object","required":["status","paused_count","total_count","failed_count"],"title":"PauseAllCampaignsResponse","description":"Outcome of pausing every campaign on the account."},"PaymentAnalyticsResponse":{"properties":{"summary":{"$ref":"#/components/schemas/PaymentSummary","description":"Totals across the whole window."},"daily":{"items":{"$ref":"#/components/schemas/DailyMetrics"},"type":"array","title":"Daily","description":"One entry per day that had activity, oldest first. A day with no transactions is absent rather than zero-filled, so chart against the window you asked for rather than assuming a contiguous series.","example":[{"date":"2026-08-25","payment_count":4,"payments":8200,"unique_customers":4}]},"available_currencies":{"items":{"type":"string"},"type":"array","title":"Available Currencies","description":"Every currency the app took money in during the window, as lowercase three-letter ISO 4217 codes. Pass one or more of these back in `currencies` to narrow the totals to a single currency.","default":[],"example":["usd","eur"]}},"type":"object","required":["summary","daily"],"title":"PaymentAnalyticsResponse","description":"An app's payment totals and its day-by-day series."},"PaymentSummary":{"properties":{"gross_revenue":{"type":"integer","title":"Gross Revenue","description":"Everything customers paid, in the smallest unit of `currency`, so `125000` is 1,250.00 in a currency with two decimal places.","default":0,"example":125000},"total_refunds":{"type":"integer","title":"Total Refunds","description":"Everything refunded to customers, in the smallest unit of `currency`, so `125000` is 1,250.00 in a currency with two decimal places.","default":0,"example":4500},"total_disputes":{"type":"integer","title":"Total Disputes","description":"Everything lost to disputes customers won, in the smallest unit of `currency`, so `125000` is 1,250.00 in a currency with two decimal places. Open disputes are not counted.","default":0,"example":0},"net_revenue":{"type":"integer","title":"Net Revenue","description":"What the app actually kept, in the smallest unit of `currency`, so `125000` is 1,250.00 in a currency with two decimal places. It is `gross_revenue` less `total_refunds` and `total_disputes`, and it can be negative in a window with more refunds than sales.","default":0,"example":120500},"transaction_count":{"type":"integer","title":"Transaction Count","description":"How many payments were taken in the window.","default":0,"example":64},"refund_count":{"type":"integer","title":"Refund Count","description":"How many refunds were issued in the window.","default":0,"example":3},"dispute_count":{"type":"integer","title":"Dispute Count","description":"How many disputes the customer won in the window.","default":0,"example":0},"unique_customers":{"type":"integer","title":"Unique Customers","description":"How many distinct customers paid in the window.","default":0,"example":51},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency","description":"Currency the amounts are in, as a lowercase three-letter ISO 4217 code. It is `null` when the window mixes currencies, in which case the amounts are sums across all of them and cannot be read as one currency, and also `null` when the window has no transactions at all.","example":"usd"}},"type":"object","title":"PaymentSummary","description":"Payment totals for the window."},"PaywallStatusContextResponse":{"properties":{"billing_organization_id":{"type":"string","title":"Billing Organization Id","description":"ID of the billing organization the paywall was evaluated against.","example":"67e0b12c4d8a3f005b21c9e4"},"user_id":{"type":"string","title":"User Id","description":"ID of the user the paywall was evaluated for.","example":"6706af53b9c1e2004a37d85f"},"evaluated_at":{"type":"string","format":"date-time","title":"Evaluated At","description":"Time the paywall condition was evaluated, as a UTC timestamp in ISO 8601 format.","example":"2026-08-02T14:30:00Z"}},"type":"object","required":["billing_organization_id","user_id","evaluated_at"],"title":"PaywallStatusContextResponse"},"Platform":{"type":"string","enum":["x","instagram","tiktok","linkedin","reddit","facebook"],"title":"Platform"},"PlatformContentPlan":{"properties":{"platform":{"anyOf":[{"$ref":"#/components/schemas/Platform"},{"type":"null"}],"description":"Platform these posts are written for.","example":"instagram"},"mode":{"anyOf":[{"$ref":"#/components/schemas/ContentMode"},{"type":"null"}],"description":"How to read the posts. A `series` is a sequence to publish in order, and a `selection` is a set of alternatives to pick one from.","example":"series"},"reasoning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reasoning","description":"Why the content for this platform takes the shape it does.","example":"Instagram rewards a consistent series, so these build on each other."},"posts":{"items":{"$ref":"#/components/schemas/SocialPost"},"type":"array","title":"Posts","description":"The generated posts for this platform."}},"type":"object","title":"PlatformContentPlan"},"PostAngle":{"type":"string","enum":["pain_point","feature_demo","social_proof","trending_hook","user_story","before_after"],"title":"PostAngle"},"PostImageResponse":{"properties":{"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url","description":"URL of the post image.","example":"https://storage.base44.com/virality/3f2504e0.png"},"plan":{"anyOf":[{"$ref":"#/components/schemas/ContentPlan"},{"type":"null"}],"description":"The content plan, with this post's `image_url` set."}},"type":"object","title":"PostImageResponse","description":"The generated image for a post, and the plan it belongs to."},"PreviewUrlResponse":{"properties":{"preview_url":{"type":"string","title":"Preview Url","description":"URL that serves the app as it currently stands in the app editor, including changes that haven't been published. Open it in a browser or an iframe.","example":"https://preview-6820f3a4e7b91d003c45a1f2.base44.app"},"preview_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preview Token","description":"Short-lived token the preview URL needs to authenticate against the preview proxy. Treat it as a credential and don't share it, because anyone holding it can load the app before it is published.","example":"pv_8f2c1a9d4b7e"},"sandbox_info":{"$ref":"#/components/schemas/SandboxInfo","description":"Details of the sandbox serving this preview, including whether it had to be started for this request."},"bridge_injected":{"type":"boolean","title":"Bridge Injected","description":"Whether the preview host injects the builder bridge. False when the platform proxy is disabled and the preview falls back to the sandbox's own host, so the editor knows the bridge's mount/paint signals will never arrive and must not wait for them.","default":true}},"type":"object","required":["preview_url","sandbox_info"],"title":"PreviewUrlResponse","description":"A preview URL for an app, and the sandbox serving it."},"PromotionalCreditSummary":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the credit.","example":"68b1c0d4e7b91d003c45a1f7"},"code":{"type":"string","title":"Code","description":"Code the credit was granted under. Empty on a credit Base44 granted directly.","example":"WELCOME50"},"credit_type":{"type":"string","title":"Credit Type","description":"Where the credit came from. A value of `GOOGLE_PROMO` is a Google Ads promotional credit, and `PLATFORM_CREDIT` is one Base44 granted.","example":"GOOGLE_PROMO"},"status":{"type":"string","title":"Status","description":"State of the credit. Either `ACTIVE`, `USED`, or `EXPIRED`.","example":"ACTIVE"},"currency_code":{"type":"string","title":"Currency Code","description":"Currency of the amounts, as an ISO 4217 code.","example":"EUR"},"amount_micros":{"type":"integer","title":"Amount Micros","description":"Face value of the credit in micros, so `50000000` is 50.00.","example":50000000},"used_micros":{"type":"integer","title":"Used Micros","description":"How much of it has been spent, in micros.","example":12000000},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"When the credit expires, or `null` on a credit that does not expire.","example":"2026-12-31T23:59:59Z"},"spendable":{"type":"boolean","title":"Spendable","description":"Whether the credit can still be spent. Rows returned only because you passed `include_settled=true` report `false`.","example":true}},"type":"object","required":["id","code","credit_type","status","currency_code","amount_micros","used_micros","spendable"],"title":"PromotionalCreditSummary","description":"One promotional credit on the app's Google Ads account."},"PublishedUrlResponse":{"properties":{"url":{"type":"string","title":"Url","description":"Public URL of the published app, built from its slug.","example":"https://my-crm-3c45a1f2.base44.app"}},"type":"object","required":["url"],"title":"PublishedUrlResponse","description":"The public URL of a published app."},"QueryEventsRequest":{"properties":{"event_name":{"type":"string","title":"Event Name","description":"Name of the event to query, exactly as the app tracked it.","example":"checkout_completed"},"q":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Q","description":"Filter expression, as a JSON object serialized to a string. Omit to return every event with this name in the time range.","example":"{\"properties.plan\": {\"in\": [\"pro\", \"elite\"]}}"},"offset":{"type":"integer","minimum":0.0,"title":"Offset","description":"Number of events to skip before the page starts. Pass the `next_offset` from the previous response to get the next page.","default":0,"example":0},"limit":{"type":"integer","maximum":1000.0,"minimum":1.0,"title":"Limit","description":"Maximum number of events to return, between 1 and 1000.","default":100,"example":100}},"type":"object","required":["event_name"],"title":"QueryEventsRequest","description":"Query request for analytics events with filter expression support."},"RefinePostPayload":{"properties":{"request":{"type":"string","maxLength":5000,"title":"Request","description":"What to change about the post, in your own words. Can't be empty.","example":"Make it shorter and drop the emoji."}},"type":"object","required":["request"],"title":"RefinePostPayload"},"RefineStrategyPayload":{"properties":{"feedback":{"type":"string","maxLength":5000,"title":"Feedback","description":"What to change about the current strategy, in your own words. Can't be empty.","example":"Focus on freelancers rather than agencies, and keep the tone less formal."}},"type":"object","required":["feedback"],"title":"RefineStrategyPayload"},"ReplaceEmailDomainRequest":{"properties":{"domain":{"type":"string","title":"Domain","description":"Domain to move to. It has to be connected to this app and different from the current one.","example":"mail.example.com"},"sender_name":{"type":"string","title":"Sender Name","description":"Name recipients see in the From line.","example":"Nordwind Furniture"},"from_email":{"type":"string","format":"email","title":"From Email","description":"Address mail is sent from once the new domain verifies.","example":"no-reply@mail.example.com"}},"type":"object","required":["domain","sender_name","from_email"],"title":"ReplaceEmailDomainRequest","description":"Request to replace email domain with a new one."},"ReplaceEmailDomainResponse":{"properties":{"domain":{"type":"string","title":"Domain","description":"The domain being moved to.","example":"mail.example.com"},"status":{"type":"string","title":"Status","description":"Where the new domain's setup got to. Only `active` sends mail, and the old domain keeps sending until this reads it. The `pending_` values mean setup is still in progress, and the `failed_` values mean it stopped and you can start it again with [Retry email domain setup](/api-reference/retry-email-domain-setup).","example":"pending_domain_verification"},"email_domain_id":{"type":"string","title":"Email Domain Id","description":"ID of this app's email configuration. It identifies the configuration, not the individual domain.","example":"68b1c0d4e7b91d003c45a1f2"},"external":{"type":"boolean","title":"External","description":"Whether you brought the new domain yourself (`true`) or bought it through Base44 (`false`).","default":false,"example":false},"dns_records":{"anyOf":[{"items":{"$ref":"#/components/schemas/EmailDnsRecordResponse"},"type":"array"},{"type":"null"}],"title":"Dns Records","description":"Records to publish for the new domain.","example":[{"name":"em1234.mail.example.com","status":"pending","ttl":300,"type":"CNAME","value":"u1234567.wl123.sendgrid.net"}]}},"type":"object","required":["domain","status","email_domain_id"],"title":"ReplaceEmailDomainResponse","description":"Response for replacing email domain."},"RepositoryConnectionResponse":{"properties":{"connection_id":{"type":"string","title":"Connection Id","description":"ID of the connection Base44 stored for this app.","example":"6890b1c4f2a7e3105d8a4472"},"repo_url":{"type":"string","title":"Repo Url","description":"URL of the repository on GitHub.","example":"https://github.com/base44/lead-tracker"},"repo_full_name":{"type":"string","title":"Repo Full Name","description":"Full repository name, as `owner/repo`.","example":"base44/lead-tracker"},"clone_urls":{"$ref":"#/components/schemas/CloneUrls","description":"URLs and CLI command for cloning the new repository."},"default_branch":{"type":"string","title":"Default Branch","description":"Default branch of the new repository. Base44 pushes the app's code to this branch.","example":"main"}},"type":"object","required":["connection_id","repo_url","repo_full_name","clone_urls","default_branch"],"title":"RepositoryConnectionResponse","description":"Result of connecting a repository."},"ResetResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Always `ok` when the reset is applied.","default":"ok","example":"ok"}},"type":"object","title":"ResetResponse","description":"Confirmation that the app's social content flow was cleared."},"ResolveConflictsResponse":{"properties":{"status":{"$ref":"#/components/schemas/ResolveOutcome","description":"How the run ended. A value of `resolved` published the resolution, `apply_pending` is still applying it, `needs_input` is waiting for an answer in the app's chat, `conflict` left the conflicts in place, and `failed` produced no outcome.","example":"resolved"}},"type":"object","required":["status"],"title":"ResolveConflictsResponse","description":"Outcome of a conflict-resolution run."},"ResolveOutcome":{"type":"string","enum":["resolved","needs_input","apply_pending","conflict","failed"],"title":"ResolveOutcome","description":"Final status of a conflict-resolution run."},"RunDetailResponse":{"properties":{"run_id":{"type":"string","title":"Run Id","description":"ID of the run.","example":"0195f2a1-4c3e-7b21-9f0d-2a5c8e1b4d77"},"workflow_id":{"type":"string","title":"Workflow Id","description":"ID of the workflow that ran.","example":"68b1c0d4e7b91d003c45a1f2"},"workflow_name":{"type":"string","title":"Workflow Name","description":"Name of that workflow at the time of the run.","default":"","example":"Email me new signups"},"trigger_type":{"type":"string","title":"Trigger Type","description":"What started the run.","default":"","example":"scheduled"},"status":{"type":"string","title":"Status","description":"How the run is going: `running`, `completed`, `failed`, or `cancelled`.","example":"completed"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At","description":"When the run started, as an ISO 8601 timestamp.","example":"2026-08-25T09:12:44Z"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At","description":"When the run finished. `null` while it is still running.","example":"2026-08-25T09:12:46Z"},"duration_ms":{"type":"integer","title":"Duration Ms","description":"How long the run took, in milliseconds.","default":0,"example":1840},"steps_count":{"type":"integer","title":"Steps Count","description":"Steps the run executed.","default":0,"example":3},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message","description":"Why the run failed. `null` when it did not fail.","example":"The email step failed because the recipient address was missing."},"is_test_run":{"type":"boolean","title":"Is Test Run","description":"`true` when the run was started by hand rather than by its trigger.","default":false,"example":false},"ai_analysis":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Analysis","description":"Stored failure explanation, once [Analyze a failed workflow run](/api-reference/analyze-a-failed-workflow-run) has generated one. `null` until then.","example":"The email step failed because the recipient address was missing from the trigger payload."},"steps":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Steps","description":"The tasks the run executed, in order. Each carries `task_name`, `task_type`, `status`, `started_at`, `completed_at`, `duration_ms` and `error_message`, plus the data that went into and came out of the step, whose shape is whatever your workflow passes around.","example":[{"completed_at":"2026-08-25T09:12:46Z","duration_ms":1840,"error_message":"","started_at":"2026-08-25T09:12:44Z","status":"completed","task_name":"send_email","task_type":"call"}]},"credits_consumed":{"type":"number","title":"Credits Consumed","description":"Credits the run used.","default":0.0,"example":0.5},"status_reason":{"type":"string","title":"Status Reason","description":"Why the run failed or was cancelled. Empty on runs that finished successfully.","default":"","example":""},"definition_version_id":{"type":"string","title":"Definition Version Id","description":"Version the run executed, which can be older than the workflow's current one. Read it with [Get workflow version](/api-reference/get-workflow-version).","default":"","example":"9f2c1a7b3e5d84f60c1b2a9e7d4f8c3b6a5e2d1f0c9b8a7e6d5c4b3a2f1e0d9c"}},"type":"object","required":["run_id","workflow_id","status"],"title":"RunDetailResponse","description":"Single run with step-by-step event log."},"RunItem":{"properties":{"run_id":{"type":"string","title":"Run Id","description":"ID of the run.","example":"0195f2a1-4c3e-7b21-9f0d-2a5c8e1b4d77"},"workflow_id":{"type":"string","title":"Workflow Id","description":"ID of the workflow that ran.","example":"68b1c0d4e7b91d003c45a1f2"},"workflow_name":{"type":"string","title":"Workflow Name","description":"Name of that workflow at the time of the run.","default":"","example":"Email me new signups"},"trigger_type":{"type":"string","title":"Trigger Type","description":"What started the run, for example `scheduled` or `entity_created`.","default":"","example":"scheduled"},"status":{"type":"string","title":"Status","description":"How the run is going: `running`, `completed`, `failed`, or `cancelled`.","example":"completed"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At","description":"When the run started, as an ISO 8601 timestamp.","example":"2026-08-25T09:12:44Z"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At","description":"When the run finished. `null` while it is still running.","example":"2026-08-25T09:12:46Z"},"duration_ms":{"type":"integer","title":"Duration Ms","description":"How long the run took, in milliseconds. `0` while it is still running.","default":0,"example":1840},"steps_count":{"type":"integer","title":"Steps Count","description":"Steps the run executed.","default":0,"example":3},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message","description":"Why the run failed. `null` when it did not fail.","example":"The email step failed because the recipient address was missing."},"is_test_run":{"type":"boolean","title":"Is Test Run","description":"`true` when the run was started by hand through Run a workflow now, rather than by its trigger.","default":false,"example":false},"credits_consumed":{"type":"number","title":"Credits Consumed","description":"Credits the run used.","default":0.0,"example":0.5},"status_reason":{"type":"string","title":"Status Reason","description":"Why the run failed or was cancelled. Empty on runs that finished successfully.","default":"","example":""}},"type":"object","required":["run_id","workflow_id","status"],"title":"RunItem"},"RunNowInfoResponse":{"properties":{"manual_run_config":{"$ref":"#/components/schemas/TriggerManualRunConfig","description":"What a manual run of this workflow needs."},"recent_runs":{"items":{"$ref":"#/components/schemas/RunItem"},"type":"array","title":"Recent Runs","description":"Runs whose payload you can replay, newest first, at most 20. Empty when the trigger needs no payload.","example":[]}},"type":"object","required":["manual_run_config"],"title":"RunNowInfoResponse","description":"Data for the builder's \"Run now\" dialog."},"RunNowRequest":{"properties":{"replay_from_run_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Replay From Run Id","description":"Run whose trigger payload to reuse for this run. Required when [Get workflow run-now options](/api-reference/get-workflow-run-now-options) reports `requires_previous_run`; leave it out otherwise.","example":"0195f2a1-4c3e-7b21-9f0d-2a5c8e1b4d77"}},"type":"object","title":"RunNowRequest"},"RunNowResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Always `started`. The run is queued, not finished.","example":"started"},"message":{"type":"string","title":"Message","description":"Confirmation text you can show someone.","example":"Run started"},"run_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Run Id","description":"ID of the run that started. Pass it to [Get workflow run](/api-reference/get-workflow-run) to follow it.","example":"0195f2a1-4c3e-7b21-9f0d-2a5c8e1b4d77"}},"type":"object","required":["status","message"],"title":"RunNowResponse"},"SandboxInfo":{"properties":{"cold_start":{"type":"boolean","title":"Cold Start","description":"Whether a new sandbox was started for this request. A value of `false` means an already running one was reused, which is why a repeat call is much faster.","example":false},"restored_from_snapshot":{"type":"boolean","title":"Restored From Snapshot","description":"Whether the new sandbox was restored from a snapshot rather than built from scratch. Always `false` when `cold_start` is `false`.","example":false},"timeout_timestamp":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timeout Timestamp","description":"When the sandbox shuts down if nothing touches it, as an ISO 8601 timestamp, or `null` when the sandbox already existed, because reusing it does not change its timeout.","example":"2026-08-02T15:30:00"}},"type":"object","required":["cold_start","restored_from_snapshot"],"title":"SandboxInfo","description":"Metadata about the sandbox serving this preview URL."},"SearchTermRow":{"properties":{"search_term":{"type":"string","title":"Search Term","description":"The term the user searched for.","example":"oak dining table"},"campaign":{"type":"string","title":"Campaign","description":"Name of the campaign whose ad the term matched.","example":"Spring sale - Search"},"impressions":{"type":"integer","title":"Impressions","description":"Impressions the term drove in the window.","example":980},"clicks":{"type":"integer","title":"Clicks","description":"Clicks the term drove in the window.","example":17},"cost_micros":{"type":"integer","title":"Cost Micros","description":"Amount spent on the term in the window, in micros of the account's currency.","example":24500000},"conversions":{"type":"number","title":"Conversions","description":"Conversions attributed to the term in the window.","example":2.0}},"type":"object","required":["search_term","campaign","impressions","clicks","cost_micros","conversions"],"title":"SearchTermRow","description":"One search term someone used before seeing an ad."},"SearchThemesRequest":{"properties":{"business_name":{"type":"string","title":"Business Name","description":"Name of the business the campaign is for.","default":"","example":"Nordwind Furniture"},"business_description":{"type":"string","title":"Business Description","description":"What the business sells, in a sentence or two.","default":"","example":"Handmade solid oak dining tables, built to order in San Francisco."},"landing_page_url":{"type":"string","title":"Landing Page Url","description":"Page the campaign sends people to. Used as context.","default":"","example":"https://nordwind-furniture.com"}},"type":"object","title":"SearchThemesRequest"},"SecurityBackendFunctionIssue":{"properties":{"file_path":{"type":"string","title":"File Path","description":"Path of the file in the app where it was found.","example":"functions/createOrder.js"},"description":{"type":"string","title":"Description","description":"What is wrong with the function and what to change.","example":"createOrder trusts the price sent by the browser. Look the price up server-side instead."}},"type":"object","required":["file_path","description"],"title":"SecurityBackendFunctionIssue","description":"A problem found in one of the app's backend functions."},"SecurityDependencyVulnerability":{"properties":{"package_name":{"type":"string","title":"Package Name","description":"Name of the npm package.","example":"axios"},"current_version":{"type":"string","title":"Current Version","description":"Version the app currently has.","example":"1.6.2"},"vulnerable_range":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vulnerable Range","description":"Range of versions the advisory covers, or `null` when the advisory does not give one.","example":"<1.7.4"},"fixed_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fixed Version","description":"First version the advisory says is fixed, or `null` when there is no fixed release.","example":"1.7.4"},"safe_fixed_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Safe Fixed Version","description":"The version Base44 checked is safe to install, which can be newer than `fixed_version`. It is `null` when no safe version was found, and absent on a finding recorded before Base44 started checking.","example":"1.7.4"},"fix_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fix Unavailable Reason","description":"Why no safe upgrade exists, when that is the case. It is `null` or absent when there is one.","example":"No released version satisfies the app's peer dependencies."},"vuln_id":{"type":"string","title":"Vuln Id","description":"Advisory identifier.","example":"GHSA-8hc4-vh64-cxmj"},"severity":{"type":"string","title":"Severity","description":"How serious it is, one of `critical`, `high`, `medium` or `low`.","example":"high"},"summary":{"type":"string","title":"Summary","description":"One-line summary of the vulnerability.","example":"Server-side request forgery in axios"},"advisory_url":{"type":"string","title":"Advisory Url","description":"Link to the full advisory.","example":"https://github.com/advisories/GHSA-8hc4-vh64-cxmj"}},"type":"object","required":["package_name","current_version","vulnerable_range","fixed_version","vuln_id","severity","summary","advisory_url"],"title":"SecurityDependencyVulnerability","description":"A known vulnerability in one of the app's npm dependencies."},"SecurityHardcodedSecret":{"properties":{"file_path":{"type":"string","title":"File Path","description":"Path of the file in the app where it was found.","example":"src/pages/Checkout.jsx"},"description":{"type":"string","title":"Description","description":"What was found and why it is a problem. The secret's own value is not included.","example":"A Stripe live key is written into the checkout page. Move it to an app secret and read it from a backend function."}},"type":"object","required":["file_path","description"],"title":"SecurityHardcodedSecret","description":"A credential written directly into the app's code."},"SecurityHeaderRecommendation":{"properties":{"flag":{"type":"string","title":"Flag","description":"Which setting to turn on, either `prevent_iframe_embedding` or `restrict_browser_features`.","example":"prevent_iframe_embedding"},"severity":{"type":"string","title":"Severity","description":"How serious it is, one of `high`, `medium` or `low`.","example":"medium"},"reason_key":{"type":"string","title":"Reason Key","description":"Stable identifier for the reason. The readable text is Base44's own translated copy, so treat this as a code to branch on rather than something to show.","example":"security.headers.reason.iframe_embedding"}},"type":"object","required":["flag","severity","reason_key"],"title":"SecurityHeaderRecommendation","description":"A recommended change to the published app's HTTP headers."},"SecurityRlsRecommendation":{"properties":{"entity_name":{"type":"string","title":"Entity Name","description":"Entity the recommendation is for, as returned by [List entity schemas](/api-reference/list-entity-schemas).","example":"Order"},"description":{"type":"string","title":"Description","description":"Why the scan recommends these rules, in plain language.","example":"Orders are readable by anyone. Restrict reads to the customer who placed the order."},"create_rule":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"boolean"},{"type":"null"}],"title":"Create Rule","description":"Recommended rule for creating a record. The rule to apply to this operation. `true` allows it for everyone, `false` blocks it outright, `null` leaves it unset, and an object is a filter matched against the record and the signed-in app user. Send the four rules to [Update entity schema](/api-reference/update-entity-schema) under the entity's `rls` to apply them.","example":{"user_condition":{"id":"{{user.id}}"}}},"read_rule":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"boolean"},{"type":"null"}],"title":"Read Rule","description":"Recommended rule for reading records. The rule to apply to this operation. `true` allows it for everyone, `false` blocks it outright, `null` leaves it unset, and an object is a filter matched against the record and the signed-in app user. Send the four rules to [Update entity schema](/api-reference/update-entity-schema) under the entity's `rls` to apply them.","example":{"user_condition":{"id":"{{user.id}}"}}},"update_rule":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"boolean"},{"type":"null"}],"title":"Update Rule","description":"Recommended rule for updating a record. The rule to apply to this operation. `true` allows it for everyone, `false` blocks it outright, `null` leaves it unset, and an object is a filter matched against the record and the signed-in app user. Send the four rules to [Update entity schema](/api-reference/update-entity-schema) under the entity's `rls` to apply them.","example":{"user_condition":{"id":"{{user.id}}"}}},"delete_rule":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"boolean"},{"type":"null"}],"title":"Delete Rule","description":"Recommended rule for deleting a record. The rule to apply to this operation. `true` allows it for everyone, `false` blocks it outright, `null` leaves it unset, and an object is a filter matched against the record and the signed-in app user. Send the four rules to [Update entity schema](/api-reference/update-entity-schema) under the entity's `rls` to apply them.","example":false}},"type":"object","required":["entity_name","description","create_rule","read_rule","update_rule","delete_rule"],"title":"SecurityRlsRecommendation","description":"A recommended row-level security rule set for one entity."},"SecurityScanFindings":{"properties":{"analysis_summary":{"type":"string","title":"Analysis Summary","description":"Plain-language summary of the scan.","example":"The app exposes orders to any signed-in user and has one hardcoded credential."},"rls_recommendations":{"items":{"$ref":"#/components/schemas/SecurityRlsRecommendation"},"type":"array","title":"Rls Recommendations","description":"Entities whose row-level security should change, one entry per entity.","example":[]},"hardcoded_secrets":{"items":{"$ref":"#/components/schemas/SecurityHardcodedSecret"},"type":"array","title":"Hardcoded Secrets","description":"Credentials written into the app's code.","example":[]},"backend_functions":{"items":{"$ref":"#/components/schemas/SecurityBackendFunctionIssue"},"type":"array","title":"Backend Functions","description":"Problems found in the app's backend functions.","example":[]},"dependency_vulnerabilities":{"items":{"$ref":"#/components/schemas/SecurityDependencyVulnerability"},"type":"array","title":"Dependency Vulnerabilities","description":"Known vulnerabilities in the app's npm dependencies. Always empty for a caller outside the dependency-scanning rollout.","example":[]},"static_code_findings":{"items":{"$ref":"#/components/schemas/SecurityStaticCodeFinding"},"type":"array","title":"Static Code Findings","description":"Problems found by reading the app's code, listing only the findings that survived the scan's own second-pass check. It is an empty list when code-reading analysis is switched off for the app, which is not the same as a clean result, so read `static_code_enabled` before concluding there is nothing to find.","example":[]},"header_recommendations":{"items":{"$ref":"#/components/schemas/SecurityHeaderRecommendation"},"type":"array","title":"Header Recommendations","description":"Recommended changes to the published app's HTTP headers. These are computed from the app's current settings on every read rather than stored with the scan.","example":[]},"core_integration_recommendation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Core Integration Recommendation","description":"Whether the app is ready for Base44's core-integration protection. `no_restricted_usage` means nothing in the app needs it, `compatible` means turning it on is safe, `would_be_blocked` means it would break the app as written, and `publish_required` means the app has to be published before this can be judged. It is `null` when the answer does not apply to this app.","example":"compatible"},"scanned_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scanned At","description":"When the scan that produced these findings ran, or `null` on a result recorded before Base44 stored the time.","example":"2026-08-25T14:05:00Z"}},"type":"object","required":["analysis_summary","rls_recommendations","hardcoded_secrets","backend_functions","dependency_vulnerabilities","static_code_findings","header_recommendations","core_integration_recommendation","scanned_at"],"title":"SecurityScanFindings","description":"What a completed security scan found."},"SecurityScanStatus":{"properties":{"status":{"type":"string","title":"Status","description":"Where the scan is. `up_to_date` means `result` reflects the app as it is now. `out_of_date` means the app changed since the last scan, so `result` is stale or absent. `none` means the app has never been scanned. `pending` means a scan is queued and `scanning` means one is running, and both can carry an earlier `result` while you wait. `scan_failed` means the last scan died, so run another.","example":"up_to_date"},"result":{"anyOf":[{"$ref":"#/components/schemas/SecurityScanFindings"},{"type":"null"}],"description":"What the scan found, or `null` when there is nothing to show. It is `null` on `none`, and also on `out_of_date` when the last scan came from a different version of the scanner, in which case only a fresh scan produces findings."},"static_code_enabled":{"type":"boolean","title":"Static Code Enabled","description":"Whether code-reading analysis is switched on for this app (`true`) or not (`false`). When it is `false`, `result.static_code_findings` comes back as an empty list, so this field is the only way to tell an analysis that found nothing from one that never ran.","example":true}},"type":"object","required":["status","result","static_code_enabled"],"title":"SecurityScanStatus","description":"The state of an app's security scan, and its findings when it has any."},"SecurityStaticCodeFinding":{"properties":{"title":{"type":"string","title":"Title","description":"Short name for the problem.","example":"Order lookup trusts a client-supplied ID"},"severity":{"type":"string","title":"Severity","description":"How serious it is, one of `critical`, `high`, `medium` or `low`.","example":"high"},"confidence":{"type":"string","title":"Confidence","description":"How sure the scan is, one of `high`, `medium` or `low`. A `low` confidence finding is worth reading before acting on.","example":"high"},"category":{"type":"string","title":"Category","description":"What kind of problem it is, one of `unauthorized_access`, `unsafe_user_input`, `exposed_sensitive_data`, `unsafe_redirect_or_external_request`, `unsafe_browser_code`, `payment_or_webhook_risk`, `file_handling_risk` or `other`.","example":"unauthorized_access"},"file_path":{"type":"string","title":"File Path","description":"Path of the file in the app where it was found.","example":"src/pages/Orders.jsx"},"line_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Line Number","description":"Line the finding points at, or `null` when the scan could not place it on one.","example":42},"evidence":{"type":"string","title":"Evidence","description":"The snippet of the app's own code the finding is about.","example":"const order = await Order.get(searchParams.get('id'))"},"attack_scenario":{"type":"string","title":"Attack Scenario","description":"How someone would exploit it.","example":"Change the id in the URL to another customer's order and read it."},"impact":{"type":"string","title":"Impact","description":"What it costs if exploited.","example":"Any signed-in user can read every order."},"recommendation":{"type":"string","title":"Recommendation","description":"What to change.","example":"Add a read rule on Order restricting it to the customer who placed it."},"standards":{"anyOf":[{"items":{"$ref":"#/components/schemas/SecurityStaticCodeStandard"},"type":"array"},{"type":"null"}],"title":"Standards","description":"Security standards the finding maps to. Absent on a finding the scan did not classify.","example":[{"framework":"CWE","id":"CWE-639","name":"Authorization Bypass Through User-Controlled Key"}]}},"type":"object","required":["title","severity","confidence","category","file_path","line_number","evidence","attack_scenario","impact","recommendation"],"title":"SecurityStaticCodeFinding","description":"A problem found by reading the app's code."},"SecurityStaticCodeStandard":{"properties":{"framework":{"type":"string","title":"Framework","description":"Which catalog the identifier belongs to.","example":"CWE"},"id":{"type":"string","title":"Id","description":"Identifier within that catalog.","example":"CWE-639"},"name":{"type":"string","title":"Name","description":"Name of the entry.","example":"Authorization Bypass Through User-Controlled Key"}},"type":"object","required":["framework","id","name"],"title":"SecurityStaticCodeStandard","description":"A security standard a static-analysis finding maps to."},"SetSecretsResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the secrets were stored.","example":true}},"type":"object","required":["success"],"title":"SetSecretsResponse","description":"Confirmation that the secrets were stored."},"SocialContentStateResponse":{"properties":{"stage":{"anyOf":[{"$ref":"#/components/schemas/ViralityStage"},{"type":"null"}],"description":"How far the app has got. The stage is `idle` before you start, then `questions`, `strategy`, `generating`, and `completed` once a plan exists.","example":"completed"},"questions":{"anyOf":[{"items":{"$ref":"#/components/schemas/ViralityQuestion"},"type":"array"},{"type":"null"}],"title":"Questions","description":"Questions from [Start social content flow](/api-reference/start-social-content-flow), or `null` if the flow hasn't started."},"analysis_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Analysis Text","description":"One-sentence analysis of the app, or `null` if the flow hasn't started.","example":"A CRM for freelancers who want to track leads without a spreadsheet."},"answers":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Answers","description":"Answers you submitted, keyed by question `id`, or `null` if none were submitted.","example":{"goal":"Get the first 100 users"}},"strategy_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Strategy Text","description":"The current content strategy, or `null` if none was generated.","example":"Lead with the spreadsheet pain, then show the app solving it."},"plan":{"anyOf":[{"$ref":"#/components/schemas/ContentPlan"},{"type":"null"}],"description":"The current content plan, or `null` if none was generated. Poll this to pick up images that finish generating after [Generate content plan](/api-reference/generate-content-plan) returns."},"builder_handle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Builder Handle","description":"Social handle taken from the `social_url` you submitted, or `null` if you never sent one.","example":"@yourhandle"},"app_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"App Name","description":"Name of the app.","example":"Freelance CRM"},"teaser":{"$ref":"#/components/schemas/TeaserSummary","description":"The standalone teaser posts, which are generated outside this flow and are not part of `plan`."}},"type":"object","title":"SocialContentStateResponse","description":"The current state of an app's social content flow."},"SocialPost":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"ID of the post. Pass it as `post_id` to [Refine a post](/api-reference/refine-a-post), [Update post content](/api-reference/update-post-content), and [Generate a post image](/api-reference/generate-a-post-image).","example":"3f2504e0-4f89-11d3-9a0c-0305e82c3301"},"platform":{"anyOf":[{"$ref":"#/components/schemas/Platform"},{"type":"null"}],"description":"Platform the post is written for.","example":"instagram"},"angle":{"anyOf":[{"$ref":"#/components/schemas/PostAngle"},{"type":"null"}],"description":"Editorial angle the post takes.","example":"pain_point"},"angle_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Angle Label","description":"Human-readable label for the angle.","example":"Pain point"},"post_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Post Number","description":"Position of this post within its platform's set, starting at 1.","example":1},"total_posts":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Posts","description":"Number of posts generated for this platform.","example":5},"suggested_day":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Suggested Day","description":"Suggested day to publish on, counted from the start of the campaign.","example":1},"rationale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rationale","description":"Why this post works for this platform and angle.","example":"Opens on the spreadsheet frustration the audience already has."},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content","description":"The post text, ready to publish. Change it with [Update post content](/api-reference/update-post-content).","example":"Still tracking leads in a spreadsheet? I built the thing I wanted instead."},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url","description":"URL of the post image, or `null` if no image was generated yet. Create one with [Generate a post image](/api-reference/generate-a-post-image).","example":"https://storage.base44.com/virality/3f2504e0.png"},"image_prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Prompt","description":"Prompt used to generate the post image, or `null` if the post has none.","example":"A freelancer closing a laptop at a tidy desk, warm morning light"},"hashtags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Hashtags","description":"Suggested hashtags, without the leading `#`.","example":["freelance","buildinpublic"]},"post_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Post Title","description":"Title for platforms that use one, such as Reddit and LinkedIn, or `null` elsewhere.","example":"I built a CRM because spreadsheets kept losing my leads"},"suggested_subreddits":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Suggested Subreddits","description":"Subreddits to consider for a Reddit post. Empty for other platforms.","example":["r/freelance","r/SideProject"]},"launch_comment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Launch Comment","description":"First comment to post under the main post, or `null` if none was generated.","example":"Happy to answer questions about how it works."},"option_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Option Label","description":"Label for this post when the platform's `mode` is `selection`, so you can tell the alternatives apart, or `null` in `series` mode.","example":"Direct and personal"},"best_for_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Best For Context","description":"When to prefer this option over the others, or `null` if not applicable.","example":"Best if your audience already knows you"}},"type":"object","title":"SocialPost"},"StartFlowResponse":{"properties":{"questions":{"items":{"$ref":"#/components/schemas/ViralityQuestion"},"type":"array","title":"Questions","description":"Questions to answer before a strategy is generated. Usually 2 or 3, with a social profile question last."},"analysis_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Analysis Text","description":"One-sentence analysis of the app, to show above the questions.","example":"A CRM for freelancers who want to track leads without a spreadsheet."}},"type":"object","title":"StartFlowResponse","description":"The opening analysis of an app and the questions it raised."},"StrategyResponse":{"properties":{"strategy_text":{"type":"string","title":"Strategy Text","description":"The content strategy, written as prose for you to review. Refine it with [Refine content strategy](/api-reference/refine-content-strategy), or accept it and call [Generate content plan](/api-reference/generate-content-plan).","default":"","example":"Lead with the spreadsheet pain your audience already feels, then show the app solving it in one screen."}},"type":"object","title":"StrategyResponse","description":"The app's social content strategy after the change."},"SubmitAnswersPayload":{"properties":{"answers":{"additionalProperties":{"type":"string"},"type":"object","title":"Answers","description":"Your answers, keyed by the `id` of each question returned by [Start social content flow](/api-reference/start-social-content-flow). Send at most 10. Answers longer than 2000 characters are truncated, and a key containing `$` or `.` or longer than 200 characters is dropped.","example":{"goal":"Get the first 100 users","target_audience":"Freelance designers"}},"social_url":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Social Url","description":"HTTPS URL of your social profile, used to match the writing voice of the generated content. Omit it to skip voice matching.","example":"https://x.com/yourhandle"}},"type":"object","required":["answers"],"title":"SubmitAnswersPayload"},"SyncEntitySchemasRequest":{"properties":{"entityNameToSchema":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Entitynametoschema","description":"The app's complete set of entities, keyed by entity name. Each value is that entity's [JSON Schema](/developers/backend/resources/entities/entity-schemas), which needs `\"type\": \"object\"` and a `properties` object, plus any `required` fields and [row-level security rules](/developers/backend/resources/entities/security) under `rls`. Any entity the app currently has that this map leaves out is deleted.","example":{"Invoice":{"name":"Invoice","properties":{"amount":{"description":"Total amount in cents","type":"number"},"status":{"enum":["draft","sent","paid"],"type":"string"}},"required":["amount"],"rls":{"read":{"created_by":"{{user.email}}"}},"type":"object"}}}},"type":"object","required":["entityNameToSchema"],"title":"SyncEntitySchemasRequest"},"SyncEntitySchemasResponse":{"properties":{"created":{"items":{"type":"string"},"type":"array","title":"Created","description":"Entities that did not exist before and were added.","example":["Invoice"]},"updated":{"items":{"type":"string"},"type":"array","title":"Updated","description":"Entities that already existed and were replaced.","example":["Customer"]},"deleted":{"items":{"type":"string"},"type":"array","title":"Deleted","description":"Entities the app had and the request left out, which were removed.","example":["LegacyOrder"]},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings","description":"Row-level security rules Base44 stored but can't enforce. The sync still applied, so an entity named here exists with a rule that isn't protecting anything. Base44 reports a rule this way only for an entity the sync adds fresh (one that didn't exist in the app before). On an entity the app already has, a new or changed rule of this kind fails the call with a 400. An unchanged unenforceable rule on an existing entity is left in place silently and doesn't appear here.","example":["Invalid RLS rule in Invoice: \"properties.total.rls.delete\" — field-level delete rules are not enforced — delete removes the whole record (there is no field-level delete gate); put the restriction in a top-level rls.delete instead."]}},"type":"object","required":["created","updated","deleted","warnings"],"title":"SyncEntitySchemasResponse","description":"What a schema sync changed, split into created, updated, and deleted entities."},"TeaserStatus":{"type":"string","enum":["generating","ready","failed"],"title":"TeaserStatus"},"TeaserSummary":{"properties":{"status":{"anyOf":[{"$ref":"#/components/schemas/TeaserStatus"},{"type":"null"}],"description":"Status of the standalone teaser posts, or `null` if none were ever requested for this app.","example":"ready"},"post":{"anyOf":[{"$ref":"#/components/schemas/SocialPost"},{"type":"null"}],"description":"The first entry of `posts`, or `null` if the teaser has none. Kept for compatibility — read `posts` instead."},"posts":{"items":{"$ref":"#/components/schemas/SocialPost"},"type":"array","title":"Posts","description":"The teaser posts, one per platform. A regeneration keeps the posts it is replacing, so these can be set while `status` is `generating`."}},"type":"object","title":"TeaserSummary","description":"The wizard-free posts shown on the Marketing home.\n\nSeparate from `plan` because they are not part of one — see `teaser.py`.\nThe model the reference renders and the one the routes return: two of these\ndrifted apart once already, so `GET /state` and `POST /teaser` both build\nthis rather than a runtime twin of it."},"TextAssetSuggestionsRequest":{"properties":{"business_name":{"type":"string","title":"Business Name","description":"Name of the business the assets are for.","default":"","example":"Nordwind Furniture"},"business_description":{"type":"string","title":"Business Description","description":"What the business sells, in a sentence or two.","default":"","example":"Handmade solid oak dining tables, built to order in San Francisco."},"asset_types":{"items":{"type":"string"},"type":"array","title":"Asset Types","description":"Which asset types to generate: `HEADLINE`, `LONG_HEADLINE`, `DESCRIPTION`, `BUSINESS_NAME`, `CALL_TO_ACTION_SELECTION`. Only the types you list come back.","example":["HEADLINE","DESCRIPTION"]},"keywords":{"items":{"type":"string"},"type":"array","title":"Keywords","description":"Words the copy should lean on.","example":["oak dining table"]},"search_themes":{"items":{"type":"string"},"type":"array","title":"Search Themes","description":"Search themes the campaign already uses, so the copy matches them.","example":["handmade oak furniture"]},"user_prompt":{"type":"string","title":"User Prompt","description":"Extra direction for the copy, in the caller's own words.","default":"","example":"Emphasise the free delivery."}},"type":"object","title":"TextAssetSuggestionsRequest"},"TimeBucket":{"properties":{"bucket":{"type":"string","title":"Bucket","description":"Start of the bucket. A date-time for intervals under a day, and a date for intervals of a day or longer.","example":"2026-08-02"},"metrics":{"additionalProperties":{"anyOf":[{"type":"number"},{"type":"integer"}]},"type":"object","title":"Metrics","description":"The requested metrics for this bucket, keyed by the `name` you gave each one.","example":{"event_count":128,"unique_users":54}}},"type":"object","required":["bucket"],"title":"TimeBucket","description":"A single time bucket with computed metrics."},"TimeseriesRequest":{"properties":{"event_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Name","description":"Name of the event to aggregate. Omit to aggregate every event the app tracks.","example":"checkout_completed"},"q":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Q","description":"Filter expression, as a JSON object serialized to a string. Same syntax as [Query analytics events](/api-reference/query-analytics-events).","example":"{\"metadata.country\": \"US\"}"},"start_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start Time","description":"Start of the time range, as a UTC timestamp in ISO 8601 format. Defaults to 30 days ago.","example":"2026-07-03T00:00:00Z"},"end_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End Time","description":"End of the time range, as a UTC timestamp in ISO 8601 format. Defaults to now.","example":"2026-08-02T00:00:00Z"},"bucket_size_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Bucket Size Ms","description":"Requested bucket size in milliseconds. Base44 rounds it down to the nearest supported interval, and up to one hour when you ask for anything finer than that. Omit to pick the finest interval that fits `max_buckets`.","example":86400000},"max_buckets":{"type":"integer","maximum":1000.0,"minimum":10.0,"title":"Max Buckets","description":"Maximum number of buckets to return, between 10 and 1000. Drives the interval when `bucket_size_ms` is omitted.","default":1000,"example":100},"metrics":{"items":{"$ref":"#/components/schemas/AggregationMetric"},"type":"array","maxItems":10,"minItems":1,"title":"Metrics","description":"Between 1 and 10 metrics to compute per bucket. Defaults to an event count named `event_count` and a distinct-user count named `unique_users`.","example":[{"function":"count","name":"event_count"},{"field":"user_id","function":"count_unique","name":"unique_users"}]}},"type":"object","title":"TimeseriesRequest","description":"Request for time-bucketed event aggregation."},"TimeseriesResponse":{"properties":{"event_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Name","description":"The event name that was aggregated, or `null` when the request covered every event.","example":"checkout_completed"},"bucket_interval":{"type":"integer","title":"Bucket Interval","description":"Interval Base44 bucketed by, in milliseconds.","example":86400000},"bucket_count":{"type":"integer","title":"Bucket Count","description":"Number of buckets returned.","example":30},"totals":{"additionalProperties":{"anyOf":[{"type":"number"},{"type":"integer"}]},"type":"object","title":"Totals","description":"Each metric computed over the whole range at once, keyed by metric name. Not the sum of the buckets, because `count_unique` counts distinct values across the range.","example":{"event_count":3840,"unique_users":612}},"buckets":{"items":{"$ref":"#/components/schemas/TimeBucket"},"type":"array","title":"Buckets","description":"The buckets, oldest first. Buckets with no matching events are omitted rather than reported as zero.","example":[{"bucket":"2026-08-01","metrics":{"event_count":112,"unique_users":47}},{"bucket":"2026-08-02","metrics":{"event_count":128,"unique_users":54}}]}},"type":"object","required":["bucket_interval","bucket_count"],"title":"TimeseriesResponse","description":"Analytics events aggregated into time buckets."},"ToggleStatusResponse":{"properties":{"workflow_id":{"type":"string","title":"Workflow Id","description":"ID of the workflow.","example":"68b1c0d4e7b91d003c45a1f2"},"status":{"type":"string","title":"Status","description":"The status the workflow actually landed in, which is not always the one you asked for.","example":"active"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message","description":"Why a restored workflow did not come back active, written for a person to read. `null` when you toggled the status yourself.","example":"The schedule already ended, so the workflow stayed inactive."}},"type":"object","required":["workflow_id","status"],"title":"ToggleStatusResponse"},"TriggerManualRunConfig":{"properties":{"requires_previous_run":{"type":"boolean","title":"Requires Previous Run","default":true}},"type":"object","title":"TriggerManualRunConfig","description":"Per-trigger behavior of the builder's manual \"Run now\" experience.\n\n``requires_previous_run``: the trigger payload references real in-app\nstate (entity records, agent conversations, connector events) that a\nsynthetic payload can't satisfy — a manual run must replay the payload\nof a previous run. Triggers that run without a payload (e.g. scheduled)\nset this to False."},"TroubleshootReply":{"properties":{"reply":{"type":"string","title":"Reply","description":"The answer, as three to five sentences of plain prose with no markdown and no code blocks. It is written for a person to read, so do not parse it. An empty string means the model returned nothing, which you cannot tell apart from a genuinely empty answer, so treat it as a failed call and retry.","example":"Your campaign is spending its full daily budget but converting below the account average, which usually points at the landing page rather than the targeting. Check that the page loads quickly on mobile and that the offer matches the ad copy. Narrowing the geo targets to your best-performing region would also concentrate the budget."}},"type":"object","required":["reply"],"title":"TroubleshootReply","description":"The assistant's answer."},"UpdateEmailDomainRequest":{"properties":{"sender_name":{"type":"string","title":"Sender Name","description":"Name recipients see in the From line. Required, so resend the current value to keep it.","example":"Nordwind Furniture"},"from_email":{"type":"string","format":"email","title":"From Email","description":"Address mail is sent from. Its domain has to be one the app already has set up for email. Required, so resend the current value to keep it.","example":"hello@example.com"}},"type":"object","required":["sender_name","from_email"],"title":"UpdateEmailDomainRequest","description":"Request to update email domain configuration."},"UpdateEmailDomainResponse":{"properties":{"sender_name":{"type":"string","title":"Sender Name","description":"The name now in use.","example":"Nordwind Furniture"},"from_email":{"type":"string","title":"From Email","description":"The address now in use, lower-cased.","example":"hello@example.com"},"domain":{"type":"string","title":"Domain","description":"The domain this configuration belongs to.","example":"example.com"},"configuration_status":{"type":"string","title":"Configuration Status","description":"Where setup stands. Updating these fields doesn't change it. Only `active` sends mail. The `pending_` values mean setup is still in progress, and the `failed_` values mean it stopped and you can start it again with [Retry email domain setup](/api-reference/retry-email-domain-setup).","example":"active"}},"type":"object","required":["sender_name","from_email","domain","configuration_status"],"title":"UpdateEmailDomainResponse","description":"Response for updating email domain configuration."},"UpdateEntitySchemaRequest":{"properties":{"entity_schema":{"additionalProperties":true,"type":"object","title":"Entity Schema","description":"The entity's full [JSON Schema](/developers/backend/resources/entities/entity-schemas), replacing the stored one. Needs `\"type\": \"object\"` and a `properties` object, plus any `required` fields and [row-level security rules](/developers/backend/resources/entities/security) under `rls`.","example":{"name":"Invoice","properties":{"amount":{"description":"Total amount in cents","type":"number"},"status":{"enum":["draft","sent","paid"],"type":"string"}},"required":["amount"],"rls":{"read":{"created_by":"{{user.email}}"}},"type":"object"}}},"type":"object","required":["entity_schema"],"title":"UpdateEntitySchemaRequest"},"UpdatePostPayload":{"properties":{"content":{"type":"string","maxLength":50000,"title":"Content","description":"The post text to save, replacing whatever the post held before. Sanitized before it is stored, so XML or HTML tags, code fences, and lines starting `system:`, `assistant:`, or `user:` are removed.","example":"Still tracking leads in a spreadsheet? I built the thing I wanted instead."}},"type":"object","required":["content"],"title":"UpdatePostPayload"},"UpdateWorkflowRequest":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"New name. Leave it out to keep the current one.","example":"Email me new signups"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"New description. Leave it out to keep the current one.","example":"Sends an email whenever a User record is created."},"definition":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Definition","description":"New definition. Sending one saves a new version. Leave it out to keep the current one.","example":{"do":[],"document":{"dsl":"1.0.0","name":"notify","version":"1.0.0"}}},"trigger":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Trigger","description":"New trigger, same shape as on create. Leave it out to keep the current one.","example":{"config":{"cron_expression":"0 9 * * *","timezone":"UTC","trigger_type":"scheduled"}}},"change_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Change Summary","description":"Note describing this change, kept in the version history.","example":"Send to the ops alias instead"}},"type":"object","title":"UpdateWorkflowRequest"},"UrlRedirectMatchType":{"type":"string","enum":["single","prefix"],"title":"UrlRedirectMatchType"},"UrlRedirectPayload":{"properties":{"source_path":{"type":"string","maxLength":512,"minLength":1,"title":"Source Path","description":"Path visitors request, starting with `/` and carrying no query string or fragment. Base44 strips a trailing slash and decodes percent-escapes before storing it, so `/old/` and `/%6Fld` are the same rule.","example":"/old-pricing"},"target_path":{"type":"string","maxLength":512,"minLength":1,"title":"Target Path","description":"Where to send the visitor. Either an internal path starting with `/`, normalized the same way as `source_path`, or an absolute `https://` URL on another site, kept as you sent it. Send it without a query string or fragment. The visitor's own query string is carried over to the destination, so `/old?utm=x` lands on `/new?utm=x`.","example":"/pricing"},"match_type":{"$ref":"#/components/schemas/UrlRedirectMatchType","description":"Use `single` to redirect that exact path, or `prefix` to redirect it and everything under it, keeping the remainder of the path. Defaults to `single`, so omitting it on an update turns an existing `prefix` rule into a `single` one and its child paths stop redirecting.","default":"single","example":"single"}},"additionalProperties":false,"type":"object","required":["source_path","target_path"],"title":"UrlRedirectPayload"},"UrlRedirectResource":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the redirect. Pass it as `redirect_id` to [Update URL redirect](/api-reference/update-url-redirect) and [Delete URL redirect](/api-reference/delete-url-redirect).","example":"68c2d1e5f3b8a4216e9b5583"},"source_path":{"type":"string","title":"Source Path","description":"The path visitors request, normalized with no trailing slash and percent-escapes decoded.","example":"/old-pricing"},"target_path":{"type":"string","title":"Target Path","description":"Where the visitor is sent. Either an internal path, normalized the same way as `source_path`, or an absolute `https://` URL on another site, kept exactly as you sent it.","example":"/pricing"},"match_type":{"$ref":"#/components/schemas/UrlRedirectMatchType","description":"How the rule matches. A `single` rule redirects that exact path, and a `prefix` rule redirects it and everything under it, keeping the remainder of the path.","example":"single"}},"type":"object","required":["id","source_path","target_path","match_type"],"title":"UrlRedirectResource","description":"One 301 redirect rule on the app's published site."},"UserStatsResponse":{"properties":{"active_users":{"type":"integer","title":"Active Users","description":"Users whose most recent event was in the last 7 days.","default":0,"example":126},"live_users":{"type":"integer","title":"Live Users","description":"Users seen in the app in the last 2 minutes.","default":0,"example":3},"inactive_7d":{"type":"integer","title":"Inactive 7D","description":"Users whose most recent event was between 7 and 30 days ago.","default":0,"example":88},"inactive_30d":{"type":"integer","title":"Inactive 30D","description":"Users whose most recent event was more than 30 days ago.","default":0,"example":41}},"type":"object","title":"UserStatsResponse","description":"How many of an app's users are active, broken down by recency."},"ValidateDefinitionRequest":{"properties":{"definition":{"additionalProperties":true,"type":"object","title":"Definition","description":"The CNCF Serverless Workflow v1.0 document to check.","example":{"do":[],"document":{"dsl":"1.0.0","name":"notify","version":"1.0.0"}}}},"type":"object","required":["definition"],"title":"ValidateDefinitionRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"ValidationResponse":{"properties":{"valid":{"type":"boolean","title":"Valid","description":"Whether the definition can be saved as-is.","example":true},"errors":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Errors","description":"What is wrong with the definition, one entry per problem. Empty when `valid` is `true`.","example":[{"code":"UNKNOWN_TASK_TYPE","message":"Task 'notify' has no known type","path":"do[0]"}]},"supported_task_types":{"items":{"type":"string"},"type":"array","title":"Supported Task Types","description":"Task types a definition may use.","example":["call","set","switch","for"]},"available_activities":{"items":{"type":"string"},"type":"array","title":"Available Activities","description":"Activities this app can call from a task.","example":["send_email","create_entity_record"]}},"type":"object","required":["valid"],"title":"ValidationResponse"},"VersionDefinitionResponse":{"properties":{"version_id":{"type":"string","title":"Version Id","description":"ID of the version, which is the SHA-256 hash of its definition.","example":"9f2c1a7b3e5d84f60c1b2a9e7d4f8c3b6a5e2d1f0c9b8a7e6d5c4b3a2f1e0d9c"},"definition":{"additionalProperties":true,"type":"object","title":"Definition","description":"The steps this version runs, as a CNCF Serverless Workflow v1.0 document.","example":{"do":[],"document":{"dsl":"1.0.0","name":"notify","version":"1.0.0"}}}},"type":"object","required":["version_id","definition"],"title":"VersionDefinitionResponse","description":"A single immutable version with its full definition."},"VersionItem":{"properties":{"version_id":{"type":"string","title":"Version Id","description":"ID of the version, which is the SHA-256 hash of its definition.","example":"9f2c1a7b3e5d84f60c1b2a9e7d4f8c3b6a5e2d1f0c9b8a7e6d5c4b3a2f1e0d9c"},"workflow_id":{"type":"string","title":"Workflow Id","description":"ID of the workflow it belongs to.","example":"68b1c0d4e7b91d003c45a1f2"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By","description":"Email of whoever saved this version.","example":"you@example.com"},"change_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Change Summary","description":"The note saved with this version, when one was given.","example":"Send to the ops alias instead"},"created_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created Date","description":"When the version was saved.","example":"2026-08-20T16:31:00Z"}},"type":"object","required":["version_id","workflow_id"],"title":"VersionItem"},"ViralityQuestion":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"ID of the question. Use it as the key in the `answers` object you pass to [Submit answers](/api-reference/submit-answers).","example":"goal"},"question":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Question","description":"Question text to show the user.","example":"What does success look like for this app?"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type","description":"How to collect the answer. A `select` question offers the listed `options`, and a `text_input` question takes free text.","example":"select"},"options":{"anyOf":[{"items":{"$ref":"#/components/schemas/ViralityQuestionOption"},"type":"array"},{"type":"null"}],"title":"Options","description":"Options to choose from on a `select` question, or `null` on a `text_input` question."},"placeholder":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Placeholder","description":"Hint text for a `text_input` question, or `null` when there is none.","example":"https://x.com/yourhandle"}},"type":"object","title":"ViralityQuestion"},"ViralityQuestionOption":{"properties":{"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Answer option to show the user. Send this value back as the answer for a `select` question.","example":"Get the first 100 users"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Short clarification of what this option means, or `null` if none was generated.","example":"Focus on early adopters who give feedback"}},"type":"object","title":"ViralityQuestionOption"},"ViralityStage":{"type":"string","enum":["idle","questions","strategy","generating","completed"],"title":"ViralityStage"},"WorkflowListItem":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the workflow.","example":"68b1c0d4e7b91d003c45a1f2"},"app_id":{"type":"string","title":"App Id","description":"ID of the app the workflow belongs to.","example":"6820f3a4e7b91d003c45a1f2"},"file_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Key","description":"Name of the workflow's file in the app's code.","example":"email-me-new-signups"},"name":{"type":"string","title":"Name","description":"Name of the workflow.","example":"Email me new signups"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"What the workflow is for.","example":"Sends an email whenever a User record is created."},"status":{"type":"string","title":"Status","description":"Whether the workflow runs: `active`, `inactive`, or `archived`.","example":"active"},"status_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Reason","description":"Why Base44 stopped the workflow itself, as a fixed code: `consecutive_failures`, `end_condition_reached`, `migration_activation_failed`, or `workflows_not_available`. `null` when you set the status yourself.","example":"consecutive_failures"},"trigger":{"additionalProperties":true,"type":"object","title":"Trigger","description":"What starts the workflow. The trigger sits under `config`, keyed by `trigger_type`.","example":{"config":{"cron_expression":"0 9 * * *","timezone":"UTC","trigger_type":"scheduled"}}},"total_runs":{"type":"integer","title":"Total Runs","description":"Runs the workflow has started, ever.","default":0,"example":48},"consecutive_failures":{"type":"integer","title":"Consecutive Failures","description":"Runs that have failed in a row.","default":0,"example":0},"last_run_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Run At","description":"When the workflow last started running. `null` before its first run.","example":"2026-08-25T09:12:44Z"},"last_run_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Run Status","description":"How that run ended: `success`, `failed`, or `cancelled`. `null` before the first run. Note this is a different set of values from a run's own `status`, which reports `completed` rather than `success`.","example":"success"},"created_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created Date","description":"When the workflow was created.","example":"2026-07-02T11:04:00Z"}},"type":"object","required":["id","app_id","name","status"],"title":"WorkflowListItem"},"WorkflowResponse":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the workflow.","example":"68b1c0d4e7b91d003c45a1f2"},"app_id":{"type":"string","title":"App Id","description":"ID of the app the workflow belongs to.","example":"6820f3a4e7b91d003c45a1f2"},"file_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Key","description":"Name of the workflow's file in the app's code. `null` on workflows saved before files were kept.","example":"email-me-new-signups"},"name":{"type":"string","title":"Name","description":"Name of the workflow.","example":"Email me new signups"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"What the workflow is for.","example":"Sends an email whenever a User record is created."},"status":{"type":"string","title":"Status","description":"Whether the workflow runs: `active`, `inactive`, or `archived`.","example":"active"},"status_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Reason","description":"Why Base44 stopped the workflow itself, as a fixed code: `consecutive_failures`, `end_condition_reached`, `migration_activation_failed`, or `workflows_not_available`. `null` when you set the status yourself.","example":"consecutive_failures"},"current_version_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Version Id","description":"Version the workflow runs today, as the SHA-256 hash of that definition. `null` until a definition is saved.","example":"9f2c1a7b3e5d84f60c1b2a9e7d4f8c3b6a5e2d1f0c9b8a7e6d5c4b3a2f1e0d9c"},"trigger":{"additionalProperties":true,"type":"object","title":"Trigger","description":"What starts the workflow. The trigger sits under `config`, keyed by `trigger_type`.","example":{"config":{"cron_expression":"0 9 * * *","timezone":"UTC","trigger_type":"scheduled"}}},"app_type_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"App Type Context","description":"Which app surface the workflow was authored against.","example":{"app_type":"user_app"}},"last_run_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Run At","description":"When the workflow last started running. `null` before its first run.","example":"2026-08-25T09:12:44Z"},"last_run_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Run Status","description":"How that run ended: `success`, `failed`, or `cancelled`. `null` before the first run. Note this is a different set of values from a run's own `status`, which reports `completed` rather than `success`.","example":"success"},"consecutive_failures":{"type":"integer","title":"Consecutive Failures","description":"Runs that have failed in a row. Resets on the next success.","default":0,"example":0},"total_runs":{"type":"integer","title":"Total Runs","description":"Runs the workflow has started, ever.","default":0,"example":48},"successful_runs":{"type":"integer","title":"Successful Runs","description":"Runs that finished successfully, ever.","default":0,"example":44},"failed_runs":{"type":"integer","title":"Failed Runs","description":"Runs that ended in an error, ever.","default":0,"example":3},"created_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created Date","description":"When the workflow was created.","example":"2026-07-02T11:04:00Z"},"updated_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated Date","description":"When the workflow was last changed.","example":"2026-08-20T16:31:00Z"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By","description":"Email of whoever created the workflow.","example":"you@example.com"},"definition":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Definition","description":"The steps the workflow runs, as a CNCF Serverless Workflow v1.0 document. `null` when no version has been saved yet. Only this endpoint returns it; the list endpoint does not.","example":{"do":[],"document":{"dsl":"1.0.0","name":"notify","version":"1.0.0"}}}},"type":"object","required":["id","app_id","name","status"],"title":"WorkflowResponse"},"WorkflowStatsRow":{"properties":{"workflow_id":{"type":"string","title":"Workflow Id","description":"ID of the workflow these counts belong to.","example":"68b1c0d4e7b91d003c45a1f2"},"total":{"type":"integer","title":"Total","description":"Runs that started in the window.","example":48},"completed":{"type":"integer","title":"Completed","description":"Runs that finished successfully.","example":44},"failed":{"type":"integer","title":"Failed","description":"Runs that ended in an error.","example":3},"cancelled":{"type":"integer","title":"Cancelled","description":"Runs that were cancelled before finishing.","example":1},"avg_duration_ms":{"type":"number","title":"Avg Duration Ms","description":"Mean wall-clock duration of the runs in the window, in milliseconds.","example":1840.5},"last_run_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Run At","description":"When the most recent run in the window started, or `null` when none ran.","example":"2026-08-25T09:12:44Z"},"last_run_status":{"type":"string","title":"Last Run Status","description":"Status of that most recent run. Empty when no run happened in the window.","example":"completed"}},"type":"object","required":["workflow_id","total","completed","failed","cancelled","avg_duration_ms","last_run_status"],"title":"WorkflowStatsRow","description":"Run counts for one workflow over the requested window."},"WorkflowSuggestion":{"properties":{"title":{"type":"string","title":"Title","description":"Short name for the suggested workflow.","example":"Email me new signups"},"description":{"type":"string","title":"Description","description":"One sentence explaining what the workflow would do.","example":"Send yourself an email whenever someone creates an account."},"prompt":{"type":"string","title":"Prompt","description":"What to say to the builder to create this workflow. Send it as `content` to [Send chat message](/api-reference/send-chat-message).","example":"Create a workflow that emails me whenever a new User record is created."}},"type":"object","required":["title","description","prompt"],"title":"WorkflowSuggestion","description":"One workflow idea generated for the app."},"WorkflowSuggestions":{"properties":{"suggestions":{"items":{"$ref":"#/components/schemas/WorkflowSuggestion"},"type":"array","title":"Suggestions","description":"The generated ideas. Can be empty when the model returns nothing usable.","example":[{"description":"Send yourself an email whenever someone creates an account.","prompt":"Create a workflow that emails me whenever a new User record is created.","title":"Email me new signups"}]}},"type":"object","required":["suggestions"],"title":"WorkflowSuggestions","description":"Workflow ideas for an app."},"WorkspaceDebtInvoice":{"properties":{"id":{"type":"string","title":"Id","description":"Base44's ID for the invoice.","example":"68b1c0d4e7b91d003c45a1fa"},"status":{"type":"string","title":"Status","description":"Where the invoice stands. `FAILED` is an invoice whose charge did not go through, and `PENDING` is one Base44 is still collecting.","example":"FAILED"},"billing_type":{"type":"string","title":"Billing Type","description":"Which billing run produced the invoice. `weekly` is the recurring charge, `reconciliation` trues up a period, `threshold` is spend-triggered, `google_monthly` mirrors an invoice Google issued, and `adjustment` is a manual correction.","example":"weekly"},"period_start":{"type":"string","title":"Period Start","description":"First day of the spend the invoice covers, as `YYYY-MM-DD`.","example":"2026-08-18"},"period_end":{"type":"string","title":"Period End","description":"Last day of the spend the invoice covers, as `YYYY-MM-DD`.","example":"2026-08-24"},"amount_micros":{"type":"integer","title":"Amount Micros","description":"Amount owed in micros of `currency_code`, so `43500000` is 43.50.","example":43500000},"currency_code":{"type":"string","title":"Currency Code","description":"Currency of `amount_micros` as a three-letter ISO 4217 code.","example":"USD"},"failed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Failed At","description":"When the charge last failed, or `null` on an invoice that has not been attempted or is still pending.","example":"2026-08-25T04:12:00Z"}},"type":"object","required":["id","status","billing_type","period_start","period_end","amount_micros","currency_code"],"title":"WorkspaceDebtInvoice","description":"The unpaid Google Ads invoice behind a workspace's billing hold."},"WorkspaceDebtPaymentMethod":{"properties":{"brand":{"type":"string","title":"Brand","description":"Card brand, as the payment provider reports it. Empty when the provider did not return one.","example":"visa"},"last4":{"type":"string","title":"Last4","description":"Last four digits of the card. Empty when the provider did not return them.","example":"4242"}},"type":"object","required":["brand","last4"],"title":"WorkspaceDebtPaymentMethod","description":"The card the workspace has on file."},"WorkspaceDebtSummary":{"properties":{"state":{"type":"string","title":"State","description":"What can be done about the balance. `no_workspace_debt` means nothing is held. `payable` means there is an invoice and a usable card. `requires_payment_method` and `requires_payment_refresh` both mean a card is needed. `pending_payment` means a charge is already in flight. `paid_recovery_incomplete` means the invoice is paid but the account is still held. `unavailable` means the balance cannot be settled right now, including when `stripe_unavailable` is `true`. `no_debt` and `not_recoverable` mean the account has no settleable balance.","example":"payable"},"account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Id","description":"ID of the Google Ads account the balance belongs to, or `null` when `state` is `no_workspace_debt`.","example":"68b1c0d4e7b91d003c45a1f8"},"app_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"App Id","description":"ID of the app that owns the held account, which is not necessarily the app in the path. The value is `null` when `state` is `no_workspace_debt`.","example":"6820f3a4e7b91d003c45a1f2"},"account_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Status","description":"Status of the held Google Ads account, or `null` when `state` is `no_workspace_debt`.","example":"BLOCKED"},"block_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Block Reason","description":"Why the account is held, or `null` when it is not held or the reason is unrecorded.","example":"PAYMENT_FAILED"},"charge_trigger_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Charge Trigger Mode","description":"How the account is billed. `cadence` is the recurring charge and `threshold` is spend-triggered. The value is `null` when `state` is `no_workspace_debt`.","example":"cadence"},"recoverable":{"type":"boolean","title":"Recoverable","description":"Whether the account is in a state Base44 can settle at all (`true`) or not (`false`).","example":true},"can_pay_now":{"type":"boolean","title":"Can Pay Now","description":"Whether there is a chargeable invoice and a usable card, so settling would go through now (`true`) or not (`false`).","example":true},"requires_payment_method":{"type":"boolean","title":"Requires Payment Method","description":"Whether a card has to be added or replaced before the balance can be settled (`true`) or not (`false`).","example":false},"stripe_unavailable":{"type":"boolean","title":"Stripe Unavailable","description":"Whether Base44 could not reach the payment provider (`true`) or reached it fine (`false`). When it is `true` the card check failed closed, so `payment_method` reads `null` and `can_pay_now` reads `false` because the check failed, not because the card is missing. Retry rather than telling someone to add a card.","example":false},"payment_method":{"anyOf":[{"$ref":"#/components/schemas/WorkspaceDebtPaymentMethod"},{"type":"null"}],"description":"The card on file, or `null` when the workspace has none and when the payment provider could not be reached."},"invoice":{"anyOf":[{"$ref":"#/components/schemas/WorkspaceDebtInvoice"},{"type":"null"}],"description":"The unpaid invoice behind the hold, or `null` when there is none to settle and when the latest one is already paid."}},"type":"object","required":["state","recoverable","can_pay_now","requires_payment_method","stripe_unavailable"],"title":"WorkspaceDebtSummary","description":"Whether the workspace owes anything for Google Ads spend."},"_ApplyBudgetRecommendationRequest":{"properties":{"recommendation_resource_name":{"type":"string","minLength":1,"title":"Recommendation Resource Name","description":"Google's identifier for the recommendation, taken from `action_payload` on the suggestion you are applying.","example":"customers/1234567890/recommendations/AbC123"},"recommended_daily_budget_micros":{"type":"integer","exclusiveMinimum":0.0,"title":"Recommended Daily Budget Micros","description":"The daily budget the suggestion showed, in micros. This confirms what you saw rather than choosing an amount: if Google's current figure differs, the call is rejected with a 409 and nothing changes.","example":45000000},"recommendation_type":{"type":"string","title":"Recommendation Type","description":"The suggestion's `recommendation_type`, recorded for auditing.","default":"","example":"CAMPAIGN_BUDGET"}},"type":"object","required":["recommendation_resource_name","recommended_daily_budget_micros"],"title":"ApplyBudgetRecommendation","description":"Request shape for the user-approved budget-recommendation apply.\n\n`recommendation_resource_name` is Google's `customers/.../recommendations/...`\nid (used for idempotency + server-side re-resolution). The route re-fetches\nthe recommendation from Google and applies Google's own recommended amount\nfor the recommendation's target campaign, so the request body amount is\nadvisory only (what the user saw) and a tampered value can't change what is\napplied. It is still bounds-checked by update_campaign_budget."}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"api_key","description":"Personal API key."}}},"security":[{"ApiKeyAuth":[]}],"servers":[{"url":"https://app.base44.com"}]}