{"openapi":"3.1.0","info":{"title":"Base44 App Management API","version":"1.0.0"},"paths":{"/api/apps/{app_id}/entities/User":{"get":{"summary":"List 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 the app's users: the people who signed up to it, or who accepted an invitation and signed in. Someone you've invited who hasn't signed in yet isn't listed.\n\nIt 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. Unless your own account has a Base44 or Wix email address, [Count app users](/api-reference/count-app-users) also leaves out accounts with one. This list includes them, so the two can differ. Row-level security on the `User` entity applies, so rules that narrow reads narrow this list too.\n\nFilter with `q`, or by passing a field name directly as a query parameter for an exact match, as in `?role=admin`. Users come back in the order they joined unless you set `sort`.\n\n<Warning>Each user 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 belonging to a user with editor access to the app, including a read-only key. Workspace API keys are not accepted.</Note>","operationId":"list_users_api_apps__app_id__entities_User_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose users you want to list.","title":"App Id"},"description":"ID of the app whose users you want to list.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"q","in":"query","required":false,"description":"Filter as a JSON object of field names and values, for example `{\"status\": \"paid\"}` for an exact match, or using an operator such as `$gt` for a comparison. See [Filtering, sorting, and paging](/developers/references/apps-api/sections/entities#filtering-sorting-and-paging) for a full list of operators.","example":"{\"role\": \"admin\"}","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of users to return, from 1 to 10000. Leave it out to get every matching user in one response.","example":100,"schema":{"type":"integer"}},{"name":"skip","in":"query","required":false,"description":"Number of users to skip before the ones you get back, 0 or more. Defaults to 0. Use it with `limit` to page through a long list.","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":"email,full_name,role","schema":{"type":"string"}}],"responses":{"200":{"description":"The app's users.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true,"description":"One user of an app.","properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ID of the user.","example":"6874b0c2e1a94d0031bb77de","title":"Id"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Email the user signs in with.","example":"jane@acme.com","title":"Email"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The user's name, or `null` if they haven't given one.","example":"Jane Cooper","title":"Full Name"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The user's role in the app. `user` and `admin` are built in, and an app can define its own.","example":"user","title":"Role"},"collaborator_role":{"anyOf":[{"const":"editor","type":"string"},{"type":"null"}],"description":"`editor` when the user can also edit the app in Base44, otherwise `null`.","example":"editor","title":"Collaborator Role"},"created_date":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the user joined the app, as a UTC timestamp in ISO 8601 format.","example":"2026-06-01T09:23:41.481000Z","title":"Created Date"},"updated_date":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the user's record last changed, as a UTC timestamp in ISO 8601 format.","example":"2026-06-04T14:07:02.115000Z","title":"Updated Date"}},"title":"AppUserRecord"},"title":"AppUsers"}}}},"400":{"description":"`q` isn't a JSON object or isn't a filter Base44 can run, `limit` or `skip` is out of range or not a whole number, or `sort` names more than one field."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app."},"404":{"description":"App not found."},"422":{"description":"A filter you passed as its own query parameter doesn't match the type the `User` schema declares for that field."},"429":{"description":"Rate limit exceeded. The base limit is 100 requests per minute, and this endpoint shares it with [Count app users](/api-reference/count-app-users). See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/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. Unless your own account has a Base44 or Wix email address, accounts with one aren't counted either.","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/User/{user_id}":{"get":{"summary":"Get app user","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 of the app's users.\n\nPass `me` as `user_id` to get your own record. Any user of the app can read their own record, by `me` or by ID. Reading another user's record needs editor access to the app, and row-level security on the `User` entity applies to it, so a user the `rls` read rule hides is reported as not found. Field-level rules on the `User` entity apply to every record, your own included.\n\nAccounts 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, are reported as not found unless the record is your own.\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 belonging to a user of the app, including a read-only key. Workspace API keys are not accepted.</Note>","operationId":"get_entity_api_apps__app_id__entities_User__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the user, as `id` in the response of [List app users](/api-reference/list-app-users), or `me` for your own record.","title":"User Id"},"description":"ID of the user, as `id` in the response of [List app users](/api-reference/list-app-users), or `me` for your own record.","example":"6874b0c2e1a94d0031bb77de"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the user belongs to.","title":"App Id"},"description":"ID of the app the user belongs to.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The user.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"AppUserRecord","description":"One user of an app.","properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ID of the user.","example":"6874b0c2e1a94d0031bb77de","title":"Id"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Email the user signs in with.","example":"jane@acme.com","title":"Email"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The user's name, or `null` if they haven't given one.","example":"Jane Cooper","title":"Full Name"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The user's role in the app. `user` and `admin` are built in, and an app can define its own.","example":"user","title":"Role"},"collaborator_role":{"anyOf":[{"const":"editor","type":"string"},{"type":"null"}],"description":"`editor` when the user can also edit the app in Base44, otherwise `null`.","example":"editor","title":"Collaborator Role"},"created_date":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the user joined the app, as a UTC timestamp in ISO 8601 format.","example":"2026-06-01T09:23:41.481000Z","title":"Created Date"},"updated_date":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the user's record last changed, as a UTC timestamp in ISO 8601 format.","example":"2026-06-04T14:07:02.115000Z","title":"Updated Date"}}}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you asked for another user's record without editor access."},"404":{"description":"App not found, or the app has no user with this ID that you can read."},"429":{"description":"Rate limit exceeded. The base limit is 150 requests per minute, and this endpoint shares it with [Get entity record](/api-reference/get-entity-record). See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}},"put":{"summary":"Update app user","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 user's role or the extra fields the app stores on them, and returns the updated user.\n\nSend `role` to change the user's role. It isn't checked against the roles the app defines, so a misspelled role is saved as sent. If the user also belongs to a group shared with the app, they keep the highest role any of their groups gives them, so the `role` in the response can be higher than the one you sent. Only the app's owner can change the owner's role.\n\nEvery other field you send is merged into the user's record. A field you leave out keeps its value. The fields aren't checked against the `User` schema, so a misspelled name is stored under that name. `email` and `full_name` belong to the user's account and are ignored, as are `id`, `collaborator_role`, and a few fields Base44 manages itself.\n\nField-level security rules on the `User` entity apply. A `role` you send is saved before the other fields are checked, so a call rejected for another field, by one of those rules or for an oversized value, changes none of the other fields but still changes the role.\n\nUpdating a user triggers the app's webhooks for the `User` entity.\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 belonging to a user with editor 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_User__user_id__put","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the user, as `id` in the response of [List app users](/api-reference/list-app-users).","title":"User Id"},"description":"ID of the user, as `id` in the response of [List app users](/api-reference/list-app-users).","example":"6874b0c2e1a94d0031bb77de"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the user belongs to.","title":"App Id"},"description":"ID of the app the user belongs to.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"UpdateAppUser","properties":{"role":{"type":"string","description":"New role for the user in the app, such as `user` or `admin`.","example":"admin"}},"description":"The fields to change. Any field other than `role` is stored on the user as sent."},"example":{"role":"admin","department":"Sales"}}}},"responses":{"200":{"description":"The updated user.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"AppUserRecord","description":"One user of an app.","properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ID of the user.","example":"6874b0c2e1a94d0031bb77de","title":"Id"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Email the user signs in with.","example":"jane@acme.com","title":"Email"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The user's name, or `null` if they haven't given one.","example":"Jane Cooper","title":"Full Name"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The user's role in the app. `user` and `admin` are built in, and an app can define its own.","example":"user","title":"Role"},"collaborator_role":{"anyOf":[{"const":"editor","type":"string"},{"type":"null"}],"description":"`editor` when the user can also edit the app in Base44, otherwise `null`.","example":"editor","title":"Collaborator Role"},"created_date":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the user joined the app, as a UTC timestamp in ISO 8601 format.","example":"2026-06-01T09:23:41.481000Z","title":"Created Date"},"updated_date":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the user's record last changed, as a UTC timestamp in ISO 8601 format.","example":"2026-06-04T14:07:02.115000Z","title":"Updated Date"}}}}}},"400":{"description":"You tried to change the app owner's role and you aren't the owner, or, on some apps, a field value is over 20,000 characters."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, a field-level security rule refuses a field you're changing, or your API key is read-only."},"404":{"description":"App not found, or the app has no user with this ID."},"422":{"description":"The body is missing, or isn't a JSON object."},"429":{"description":"Rate limit exceeded. The base limit is 70 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}},"delete":{"summary":"Remove app user","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 user from the app.\n\nThe user loses access straight away, including any editor access they had to the app. Their invitation or access request is deleted too, so they aren't added back the next time they sign in, unless they belong to a group shared with the app, which lets them back in through the group. To give them access again, invite them with [Invite app users](/api-reference/invite-app-users). You can't remove the app's owner.\n\nRemoving a user triggers the app's webhooks for the `User` entity.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor 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_User__user_id__delete","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the user, as `id` in the response of [List app users](/api-reference/list-app-users).","title":"User Id"},"description":"ID of the user, as `id` in the response of [List app users](/api-reference/list-app-users).","example":"6874b0c2e1a94d0031bb77de"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the user belongs to.","title":"App Id"},"description":"ID of the app the user belongs to.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The user was removed.","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 editor access to this app, the user is the app's owner, or your API key is read-only."},"404":{"description":"App not found, or the app has no user with this ID."},"429":{"description":"Rate limit exceeded. The base limit is 70 requests every 30 seconds. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/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, optionally narrowed by a filter.\n\nPass the same `q` filter [List entity records](/api-reference/list-entity-records) accepts to count a subset, for example `{\"status\": \"open\"}`. The 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. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","example":"Invoice"},{"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":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional filter, a JSON object in the same form as `q` on List entity records.","title":"Q"},"description":"Optional filter, a JSON object in the same form as `q` on List entity records."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntityCountResponse"}}}},"400":{"description":"`q` isn't a JSON object or isn't a filter Base44 can run, for example one with an unknown operator or an `id` that isn't a record ID, or the entity is `User`."},"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."},"429":{"description":"Rate limit exceeded. The base limit is 70 requests per minute, and this endpoint shares it with [List entity records](/api-reference/list-entity-records). See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/entities/{entity_name}/v2/list":{"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 one page of the records in one of the app's entities, with a cursor for the next page. Deleted records are left out.\n\nUse it for exports, sync jobs, and any loop over a large entity. Every page costs the same however far in you are, and records deleted between pages don't shift where the next page starts.\n\nFilter records with the `q` parameter, or by passing one of the entity's own field names directly as a query parameter for an exact match. For example, `?status=paid` is equivalent to `q={\"status\": \"paid\"}`. Only `q` supports comparisons like `$gt`. An unrecognized parameter name is still treated as a filter, so a misspelled field name matches nothing rather than returning an error. Choose which fields come back with `fields`.\n\nFor the next page, pass `next_cursor` from the response as `cursor`. The cursor carries the filter, sort, and fields, so you don't need to repeat them, and repeating one with a different value is rejected. Stop when `has_more` is `false`. Every page holds `limit` records except the last. A cursor only works for the user it was issued to, on the same app and entity.\n\nRecords come back newest first unless you set `sort`. Records with no value in the sort field come first in ascending order and last in descending order. Records that share the same sort value are each returned once. A record whose stored value in the sort field isn't the type the entity's schema declares is left out.\n\nWith `distinct`, `items` holds the distinct values of that field among the matching records instead of records, in ascending order and without `null`. Each element of an array field counts as a value. `sort` and `fields` can't be combined with it, `limit` goes up to 1000, and an entity with field-level read rules doesn't support it.\n\nRow-level security applies, so you only get the records the entity's `rls` read rule lets your credential see. The `User` entity isn't supported. Use [List app users](/api-reference/list-app-users) instead.\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_v2_api_apps__app_id__entities__entity_name__v2_list_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. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","example":"Invoice"},{"name":"q","in":"query","required":false,"description":"Filter as a JSON object of field names and values, for example `{\"status\": \"paid\"}` for an exact match, or using an operator such as `$gt` for a comparison. See [Filtering, sorting, and paging](/developers/references/apps-api/sections/entities#filtering-sorting-and-paging) for a full list of operators.","example":"{\"status\": \"paid\"}","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Number of records on each page, from 1 to 5000, or up to 1000 with `distinct`. Defaults to 100, and a higher value is treated as the maximum.","example":500,"schema":{"type":"integer","default":100}},{"name":"sort","in":"query","required":false,"description":"Single field to sort by, prefixed with `-` for descending. For example, `-created_date` returns newest first. Defaults to `-created_date`. Sort by a field every record carries, such as `created_date`, or by one the entity's schema declares as a string, number, integer, or boolean.","example":"-created_date","schema":{"type":"string","default":"-created_date"}},{"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"}},{"name":"cursor","in":"query","required":false,"description":"Pagination cursor from previous response. Leave it out for the first page.","example":"gAAAAABn7vJ...","schema":{"type":"string"}},{"name":"distinct","in":"query","required":false,"description":"Field whose distinct values to return instead of records, in ascending order.","example":"status","schema":{"type":"string"}}],"responses":{"200":{"description":"One page of the entity's records.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"EntityRecordPage","description":"One page of records and the cursor for the next one.","properties":{"items":{"type":"array","items":{"anyOf":[{"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","type":"object"},{"type":"string"},{"type":"number"},{"type":"boolean"}]},"description":"The page's records in the requested sort order, or with `distinct` the field's values in ascending order.","example":[{"id":"6886b8d390dc7e2f4a2c91b3","created_date":"2026-06-01T09:23:41.481000","updated_date":"2026-06-04T14:07:02.115000","created_by":"jane@acme.com","created_by_id":"6874b0c2e1a94d0031bb77de","is_sample":false,"amount":4200,"status":"draft","customer_email":"jane@acme.com"}]},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Cursor for fetching the next page. The value is `null` if there are no more pages.","example":"gAAAAABn7vJ..."},"has_more":{"type":"boolean","description":"Whether there are more items to fetch.","example":true}},"required":["items","next_cursor","has_more"]}}}},"400":{"description":"The cursor is invalid, was issued to another credential, or was issued for a different filter, sort, fields, or `distinct`. Also returned when `skip` is set, `limit` is below 1 or not a whole number, `q` isn't a filter Base44 can run, `sort` names more than one field or a field it can't sort by, `distinct` names a field the schema doesn't declare or is combined with `sort` or `fields`, or the entity is `User`."},"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."},"429":{"description":"Rate limit exceeded. The base limit is 70 requests per minute, and this endpoint shares it with [Count entity records](/api-reference/count-entity-records). A page with `distinct` also counts against a separate limit of 15 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/entities/{entity_name}/aggregate":{"post":{"summary":"Aggregate 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\nComputes counts, totals, averages, minimums, maximums and distinct counts over one of the app's entities, grouped by the fields you choose, without returning the records.\n\nPick the records with `query`, group them with `group_by`, `date_bucket`, or both, and name the values to compute. For example, `{\"group_by\": \"status\", \"sum\": \"amount\"}` returns one row per status with its record count and the total of `amount`. Leave out `group_by` and `date_bucket` to get one row over every matching record. That row is returned even when no record matches, with `0` for counts and totals and `null` for averages, minimums and maximums, unless `having` rules it out.\n\nFields can be any field the entity's schema declares, or one every record carries, such as `created_date` or `created_by`. `sum` and `avg` need fields the schema declares as numbers, and `min`, `max` and `count_distinct` need fields it declares as a string, number, integer or boolean. A stored value of another type is skipped. A `date_bucket` on `created_date` or `updated_date` returns the start of each period as a timestamp. On a date field from the schema it returns the matching part of the stored text, such as `2026-06` for a month, and a stored value that isn't a date is grouped under `null`.\n\nDeleted records are left out, and row-level security applies, so the values cover only the records the entity's `rls` read rule lets your credential see. An aggregate reads every matching record and must finish within 30 seconds, with a response under 256 KB. Narrow `query` when it doesn't. The `User` entity and entities with field-level read rules aren't supported.\n\nThe camelCase spellings `groupBy`, `dateBucket` and `countDistinct` are accepted too.\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":"aggregate_entities_api_apps__app_id__entities__entity_name__aggregate_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. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","example":"Invoice"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AggregateSpec"}}}},"responses":{"200":{"description":"The computed values.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"EntityAggregateResponse","description":"The computed values, one row per group.","properties":{"rows":{"description":"One row per group. A row holds each group field and the `date_bucket` field under its own name, then `count` and the `sum_<field>`, `avg_<field>`, `min_<field>`, `max_<field>` and `count_distinct_<field>` values you asked for. In those computed names, a dot in the field name becomes `_`. Date values, such as a `date_bucket` or a `min_created_date`, are UTC timestamps in ISO 8601 format with an offset.","example":[{"count":128,"status":"paid","sum_amount":20480.5}],"items":{"additionalProperties":true,"type":"object"},"title":"Rows","type":"array"},"truncated":{"description":"`true` when more groups matched than `limit`, so some were left out.","example":false,"title":"Truncated","type":"boolean"}},"required":["rows","truncated"]}}}},"400":{"description":"A field isn't declared in the entity's schema or has the wrong type for what you asked, `group_by` names more than 4 fields, nothing is left to compute, `having` or `sort` names a field the request doesn't compute or group by, two names collide, `query` isn't a filter Base44 can run, the aggregate ran past 30 seconds or 256 KB, or the entity is `User` or has field-level read rules."},"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":"The body has a field not documented here, or `limit` is outside 1 to 1000."},"429":{"description":"Rate limit exceeded. The base limit is 15 requests per minute: an aggregate reads every matching record, so its limit is separate from list and lower. Pages of [List entity records](/api-reference/list-entity-records) with `distinct` count against it too. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/entities/{entity_name}/upsert":{"post":{"summary":"Upsert 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\nCreates or updates up to 500 records in one of the app's entities in one call, matching them to stored records by the fields you name in `key`.\n\nFor each record you send, a stored record with the same values in every `key` field is updated, and otherwise a new record is created. For example, with `\"key\": \"order_number\"`, re-sending an order updates it instead of adding a copy, so a sync job can send the same records again safely. An update merges like [Update entity record](/api-reference/update-entity-record), so a field you leave out keeps its value. Only the fields the entity's schema declares are stored, so a misspelled name is left out of the record instead of failing the call. Base44 always assigns `id`, `created_date`, `updated_date`, `created_by`, and `created_by_id` automatically, and ignores any of them you send.\n\n`key` fields must be fields the entity's schema declares as a string, number, integer or boolean, and every record needs a value for each of them. Values are compared as the schema stores them, so `\"42\"` matches a stored `42` in a number field. When two records you send share a key, the later one wins. When several stored records share a key, the newest is updated.\n\nRow-level security applies. Only stored records the entity's `rls` update rule lets you change are matched, so a record you can't change gets a new copy rather than an update, and new records must be covered by the `rls` create rule. Every record is checked before any is written, and one that fails rejects the call. If a call fails while it's writing, some records can already be stored, and sending it again finishes the job. Wait for the first call to finish before you retry: there's no `Idempotency-Key`, so two calls running at once can both create the same new record.\n\n`records` in the response lists the created records first, then the updated ones. Like [Create entity records](/api-reference/create-entity-records), this doesn't trigger the app's webhooks, automations, or workflows.\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":"upsert_entities_api_apps__app_id__entities__entity_name__upsert_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. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","example":"Invoice"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertPayload"}}}},"responses":{"200":{"description":"How many records were created and updated, and the records themselves.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"UpsertEntityRecordsResult","description":"How many records were created and updated, and the records themselves.","properties":{"created":{"type":"integer","description":"Number of new records created.","example":1},"updated":{"type":"integer","description":"Number of stored records updated.","example":1},"records":{"type":"array","items":{"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","type":"object"},"title":"EntityRecords","description":"The created records, then the updated ones.","example":[{"id":"6886b8d390dc7e2f4a2c91b4","created_date":"2026-06-05T08:12:44.902000Z","updated_date":"2026-06-05T08:12:44.902000Z","created_by":"jane@acme.com","created_by_id":"6874b0c2e1a94d0031bb77de","is_sample":false,"order_number":"A-1002","status":"draft","amount":1800},{"id":"6886b8d390dc7e2f4a2c91b3","created_date":"2026-06-01T09:23:41.481000Z","updated_date":"2026-06-05T08:12:44.915000Z","created_by":"jane@acme.com","created_by_id":"6874b0c2e1a94d0031bb77de","is_sample":false,"order_number":"A-1001","status":"paid","amount":4200}]}},"required":["created","updated","records"]}}}},"400":{"description":"`records` is empty or holds more than 500 records, a `key` field isn't a string, number, integer or boolean field the entity's schema declares, a record has no value for a `key` field or one that doesn't fit its type, a value is over 20,000 characters on an app that limits field size, or the entity is `User`."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the entity's `rls` create rule or a field-level rule doesn't cover one of the records, or your API key is read-only."},"404":{"description":"App not found, or the app has no entity with this name."},"422":{"description":"`records` or `key` is missing, `records` isn't an array of JSON objects, or a new record is missing a required field the entity's schema declares or has a value that doesn't match its type."},"429":{"description":"Rate limit exceeded. The base limit is 25 requests per minute, and this endpoint shares it with [Create entity records](/api-reference/create-entity-records). See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/entities/{entity_name}":{"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\nOnly the fields the entity's schema declares are stored, so a misspelled name is left out of the record instead of failing the call. Base44 always assigns `id`, `created_date`, `updated_date`, `created_by`, and `created_by_id` automatically, and ignores any of them you send.\n\nRow-level security applies, so the call is rejected when the entity's `rls` create rule doesn't cover the record you send.\n\nCreating a record triggers the app's webhooks and any automation or workflow that listens for this entity.\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. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","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":"On some apps, a field value over 20,000 characters is rejected. Store large content with the UploadPublicFile integration and use the returned URL instead."},"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, a required field the entity's schema declares is missing, or a value doesn't match the type the schema declares for that field."},"429":{"description":"Rate limit exceeded. The base limit is 80 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}},"delete":{"summary":"Delete 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\nDeletes every record in one of the app's entities that matches a filter, and returns how many it deleted.\n\nSend the filter as the body, written the same way as the `q` parameter of [List entity records](/api-reference/list-entity-records). For example, `{\"status\": \"cancelled\"}` deletes every cancelled record. The body is required, and an empty object, `{}`, deletes every record in the entity.\n\nRow-level security applies, so only the matching records the entity's `rls` delete rule lets you remove are deleted. The rest are left alone rather than failing the call.\n\nThe records move to the entity's trash rather than being erased. Bring them back with [Restore entity records](/api-reference/restore-entity-records), or erase one for good with [Permanently delete entity record](/api-reference/permanently-delete-entity-record).\n\nUnlike [Delete entity record](/api-reference/delete-entity-record), this doesn't trigger the app's webhooks, automations, or workflows.\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_entities_api_apps__app_id__entities__entity_name__delete","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. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","example":"Invoice"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"DeleteEntityRecords","description":"Filter selecting the records to delete, in the same form as `q`. Send `{}` to delete every record in the entity."},"example":{"status":"cancelled"}}}},"responses":{"200":{"description":"How many records were deleted.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"DeleteEntityRecordsResponse","description":"How many records were deleted.","properties":{"success":{"description":"Always `true`. A delete that doesn't happen returns an error instead.","example":true,"title":"Success","type":"boolean"},"deleted":{"description":"Number of records moved to the trash. `0` when no record matched the filter.","example":3,"title":"Deleted","type":"integer"}},"required":["success","deleted"]}}}},"400":{"description":"The filter isn't one Base44 can run, for example an unknown operator."},"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 is `User`, whose records can't be deleted in bulk."},"422":{"description":"The body is missing, or isn't a JSON object."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests every 30 seconds. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/entities/{entity_name}/bulk":{"post":{"summary":"Create 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\nAdds several records to one of the app's entities in one call, and returns them in the order you sent them.\n\nSend a JSON array of records, each written the same way as the body of [Create entity record](/api-reference/create-entity-record). Only the fields the entity's schema declares are stored, so a misspelled name is left out of the record instead of failing the call. Base44 always assigns `id`, `created_date`, `updated_date`, `created_by`, and `created_by_id` automatically, and ignores any of them you send. An empty array creates nothing and returns an empty array.\n\nEvery record is checked before any is written. If one record doesn't match the entity's schema, or the entity's `rls` create rule or a field-level rule doesn't cover it, the call is rejected and no records are created.\n\nThere's no fixed limit on how many records one call creates, but the whole array is written before the response comes back, so split a very large import into several calls.\n\nEach record is new, so retrying a call that already succeeded creates every record again. If a call fails while it's writing, some of the records can already be stored, so check with [List entity records](/api-reference/list-entity-records) before you retry.\n\nUnlike [Create entity record](/api-reference/create-entity-record), this doesn't trigger the app's webhooks, automations, or workflows.\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":"bulk_create_entities_api_apps__app_id__entities__entity_name__bulk_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. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","example":"Invoice"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"CreateEntityRecords","description":"The records to create, each a flat JSON object of the fields the entity's schema declares."},"example":[{"amount":4200,"status":"draft","customer_email":"jane@acme.com"},{"amount":1800,"status":"draft","customer_email":"jane@acme.com"}]}}},"responses":{"200":{"description":"The created records.","content":{"application/json":{"schema":{"type":"array","items":{"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","type":"object"},"title":"EntityRecords"}}}},"400":{"description":"On some apps, a field value over 20,000 characters is rejected. Store large content with the UploadPublicFile integration and use the returned URL instead."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the entity's `rls` create rule or a field-level rule doesn't cover one of the records, or your API key is read-only."},"404":{"description":"App not found, or the app has no entity with this name."},"405":{"description":"The entity is `User`, whose records can't be created in bulk."},"422":{"description":"The body isn't an array of JSON objects, or a record is missing a required field the entity's schema declares or has a value that doesn't match its type."},"429":{"description":"Rate limit exceeded. The base limit is 25 requests per minute, and this endpoint shares it with [Upsert entity records](/api-reference/upsert-entity-records). See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}},"put":{"summary":"Update 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\nChanges several records in one of the app's entities, each with its own fields, and returns the updated records in the order you sent them.\n\nSend a JSON array of up to 500 objects. Each one carries the `id` of a record and the fields to change on it, and an `id` can appear only once. Like [Update entity record](/api-reference/update-entity-record), this merges rather than replaces, so a field you leave out keeps its value. Fields the entity's schema doesn't declare are left out of the record instead of failing the call.\n\nEvery record is checked before any is written. If one `id` matches no record the entity's `rls` update rule lets you change, or one record doesn't match the entity's schema or a field-level rule, the call is rejected and no records change.\n\nSending the same array again sets the same values, so a retry is safe. Unlike [Update entity record](/api-reference/update-entity-record), this doesn't trigger the app's webhooks, automations, or workflows.\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":"bulk_update_entities_api_apps__app_id__entities__entity_name__bulk_put","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. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","example":"Invoice"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["id"],"properties":{"id":{"type":"string","description":"ID of the record to change, as `id` in the response of [List entity records](/api-reference/list-entity-records).","example":"6886b8d390dc7e2f4a2c91b3"}}},"title":"UpdateEntityRecords","description":"The records to change, each with its `id` and the fields to set on it."},"example":[{"id":"6886b8d390dc7e2f4a2c91b3","status":"paid"},{"id":"6886b8d390dc7e2f4a2c91b4","amount":1800}]}}},"responses":{"200":{"description":"The updated 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":"The array is empty or holds more than 500 items, an item has no `id` or an `id` that isn't a string, or an `id` appears twice. On some apps, a field value over 20,000 characters is rejected too."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the entity's `rls` update rule or a field-level rule refuses a change, or your API key is read-only."},"404":{"description":"App not found, the app has no entity with this name, or an `id` matches no record the entity's `rls` update rule lets you change."},"422":{"description":"The body isn't an array of JSON objects, or a value doesn't match the type the entity's schema declares for that field."},"429":{"description":"Rate limit exceeded. The base limit is 25 requests per minute, and this endpoint shares it with [Update entity records by filter](/api-reference/update-entity-records-by-filter). See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/entities/{entity_name}/import":{"post":{"summary":"Import entity records from CSV","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nCreates records in one of the app's entities from a CSV file, and returns the records it created.\n\nSend the file as `multipart/form-data` in a field named `file`. The first row has to be a header row with no blank or repeated column names. Commas, semicolons, and tabs all work as the delimiter, and the file can be up to 10 MB.\n\nBase44 pairs each of the entity's fields with the column of the same name, ignoring case, spaces, and punctuation. For any field it can't pair by name, an AI model picks a matching column or leaves the field out. Columns that match no field are ignored, and every required field needs a column.\n\nThe import is all or nothing. Every row is checked against the entity's schema before anything is written, and if one row fails, no records are created. A file Base44 can't import still returns a successful response, with `status` set to `error` and the reason in `details`, so read `status` before `output`.\n\nEach row becomes a new record, so importing the same file twice creates every record twice. Row-level security applies, so the whole call is rejected when the entity's `rls` create rule doesn't cover one of the rows. Imported records don't trigger the app's webhooks, automations, or workflows.\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":"import_entities_api_apps__app_id__entities__entity_name__import_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. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","example":"Invoice"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","title":"ImportEntityRecords","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"The CSV file to import, with a header row naming each column. Up to 10 MB."}}}}}},"responses":{"200":{"description":"The import's outcome, including when the file couldn't be imported.","content":{"application/json":{"schema":{"type":"object","title":"ImportEntityRecordsResult","description":"The outcome of a CSV import.","properties":{"status":{"type":"string","enum":["success","error"],"description":"Whether the import went through. Either `\"success\"` or `\"error\"`. On `error` no records were created.","example":"success"},"details":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"What happened, in plain language. On `error` it says why the file couldn't be imported, for example a required field no column matched.","example":"Successfully imported 2 entities with RLS enforcement"},"output":{"anyOf":[{"type":"array","items":{"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","type":"object"}},{"type":"null"}],"description":"The records the import created, in the same shape as [List entity records](/api-reference/list-entity-records) returns them, or `null` when `status` is `error`.","example":[{"id":"6886b8d390dc7e2f4a2c91b3","created_date":"2026-06-01T09:23:41.481000Z","updated_date":"2026-06-01T09:23:41.481000Z","created_by":"jane@acme.com","created_by_id":"6874b0c2e1a94d0031bb77de","is_sample":false,"amount":4200,"status":"draft","customer_email":"jane@acme.com"}]}},"required":["status","details","output"]}}}},"400":{"description":"The file is over 10 MB, or on some apps a value is over 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 one of the rows, or your API key is read-only."},"404":{"description":"App not found, or the app has no entity with this name."},"405":{"description":"The entity is `User`, whose records can't be imported."},"422":{"description":"The request has no `file` field."},"429":{"description":"Rate limit exceeded. The base limit is 20 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/entities/{entity_name}/update-many":{"patch":{"summary":"Update entity records by filter","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nApplies the same change to up to 500 records in one of the app's entities that match a filter, and returns how many changed.\n\nSend `query`, a filter written the same way as the `q` parameter of [List entity records](/api-reference/list-entity-records), and `data`, the change written as MongoDB update operators on the entity's field names. For example, `{\"query\": {\"status\": \"draft\"}, \"data\": {\"$set\": {\"status\": \"sent\"}}}` marks every draft as sent. A field can appear in only one operator per call, and the operators you can use are:\n- `$set`, `$unset`, and `$rename`\n- `$inc`, `$mul`, `$min`, and `$max`\n- `$currentDate`\n- `$addToSet`, `$push`, and `$pull`\n\nThe values aren't checked against the entity's schema, so a field the schema doesn't declare, or a value of another type, is stored as sent.\n\nRow-level security applies. The filter only reaches records the entity's `rls` update rule lets you change, and the rest are left alone rather than failing the call. If a field-level rule refuses the change on any matching record, the call is rejected and no records change.\n\nWhen more than 500 records match, 500 of them change and `has_more` is `true`. Call again to change the rest, with a filter that no longer matches the records you've already changed, as the draft filter in the example does. A filter that still matches them changes the same records again.\n\nRepeating `$set`, `$unset`, `$rename`, `$min`, `$max`, `$addToSet`, or `$pull` leaves the records as the first call did, so it's safe to retry. Repeating `$inc`, `$mul`, `$push`, or `$currentDate` applies the change a second time.\n\nUnlike [Update entity record](/api-reference/update-entity-record), this doesn't trigger the app's webhooks, automations, or workflows.\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_many_entities_api_apps__app_id__entities__entity_name__update_many_patch","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. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","example":"Invoice"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","title":"UpdateEntityRecordsByFilter","required":["query","data"],"properties":{"query":{"type":"object","additionalProperties":true,"description":"Filter selecting the records to change, in the same form as `q`. Send `{}` to match every record.","example":{"status":"draft"}},"data":{"type":"object","additionalProperties":true,"description":"The change, as one or more update operators, each mapping field names to values.","example":{"$set":{"status":"sent"}}}}},"example":{"query":{"status":"draft"},"data":{"$set":{"status":"sent"}}}}}},"responses":{"200":{"description":"How many records changed.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"UpdateEntityRecordsByFilterResponse","description":"How many records a filtered update changed.","properties":{"success":{"description":"Always `true`. An update that doesn't happen returns an error instead.","example":true,"title":"Success","type":"boolean"},"updated":{"description":"Number of matching records the call changed. A record that already held the new values still counts, because its `updated_date` changes, and `0` means no record matched.","example":42,"title":"Updated","type":"integer"},"has_more":{"description":"Whether more than 500 records matched, so only 500 of them were changed and more may be left.","example":false,"title":"Has More","type":"boolean"}},"required":["success","updated","has_more"]}}}},"400":{"description":"`data` is empty, uses an operator that isn't supported, isn't an object for each operator, or names a field in more than one operator. Also returned when `query` isn't a filter Base44 can run, for example one with an `id` that isn't a record ID, when an operator doesn't fit the type a field holds, or, on some apps, when a value is over 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 or a field-level rule refuses the change on a matching record, or your API key is read-only."},"404":{"description":"App not found, or the app has no entity with this name."},"405":{"description":"The entity is `User`, whose records can't be updated in bulk."},"422":{"description":"The body is missing `query` or `data`, or one of them isn't a JSON object."},"429":{"description":"Rate limit exceeded. The base limit is 25 requests per minute, and this endpoint shares it with [Update entity records](/api-reference/update-entity-records). See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/entities/{entity_name}/trash":{"get":{"summary":"List deleted 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 deleted from one of the app's entities that are still in its trash.\n\nA record lands here when [Delete entity record](/api-reference/delete-entity-record) removes it, and stays until you bring it back with [Restore entity record](/api-reference/restore-entity-record) or erase it with [Permanently delete entity record](/api-reference/permanently-delete-entity-record). Row-level security doesn't apply, so you get every deleted record in the entity. The `User` entity has no trash, because [Remove app user](/api-reference/remove-app-user) deletes a user outright, so asking for its trash returns a 404.\n\nFilter and sort the same way as [List entity records](/api-reference/list-entity-records). Page with `skip` and `limit`. The 5000 record cap doesn't apply here, so leave out `limit` to get the whole trash in one response.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"list_deleted_entities_api_apps__app_id__entities__entity_name__trash_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. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","example":"Invoice"},{"name":"q","in":"query","required":false,"description":"Filter as a JSON object of field names and values, for example `{\"status\": \"paid\"}` for an exact match, or using an operator such as `$gt` for a comparison. See [Filtering, sorting, and paging](/developers/references/apps-api/sections/entities#filtering-sorting-and-paging) for a full list of operators.","example":"{\"status\": \"paid\"}","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of deleted records to return, from 1 to 10000. Leave it out to get every matching deleted record in one response.","example":100,"schema":{"type":"integer"}},{"name":"skip","in":"query","required":false,"description":"Number of deleted records to skip before the ones you get back, 0 or more. Defaults to 0. Use it with `limit` to page through a long list.","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 deleted records.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true,"description":"One record in an entity's trash.","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"},"deleted_date":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the record was deleted, as a UTC timestamp in ISO 8601 format.","example":"2026-06-05T11:42:18.207000","title":"Deleted Date"}},"title":"DeletedEntityRecord"},"title":"DeletedEntityRecords"}}}},"400":{"description":"`q` isn't a JSON object or isn't a filter Base44 can run, `limit` or `skip` is out of range or not a whole number, or `sort` names more than one field."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor 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 is `User`."},"422":{"description":"A filter you passed as its own query parameter doesn't match the type the entity's schema declares for that field."},"429":{"description":"Rate limit exceeded. The base limit is 70 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/entities/{entity_name}/trash/restore":{"put":{"summary":"Restore 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\nBrings back every deleted record in the entity's trash that matches a filter, and returns how many it restored.\n\nSend the filter as the body, written the same way as the `q` parameter of [List entity records](/api-reference/list-entity-records). For example, `{\"id\": {\"$in\": [\"6886b8d390dc7e2f4a2c91b3\", \"6886b8d390dc7e2f4a2c91b4\"]}}` restores those two records. An empty object, `{}`, restores everything in the trash.\n\nUnlike [Restore entity record](/api-reference/restore-entity-record), this doesn't trigger the app's webhooks.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"restore_entities_api_apps__app_id__entities__entity_name__trash_restore_put","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. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","example":"Invoice"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"RestoreEntityRecords","description":"Filter selecting the deleted records to restore, in the same form as `q`. Send `{}` to restore everything in the trash."},"example":{"id":{"$in":["6886b8d390dc7e2f4a2c91b3","6886b8d390dc7e2f4a2c91b4"]}}}}},"responses":{"200":{"description":"How many records were restored.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"RestoreEntityRecordsResponse","description":"How many deleted records were restored.","properties":{"success":{"description":"Always `true`. A restore that doesn't happen returns an error instead.","example":true,"title":"Success","type":"boolean"},"restored":{"description":"Number of records brought back. `0` when nothing in the trash matched the filter.","example":3,"title":"Restored","type":"integer"}},"required":["success","restored"]}}}},"400":{"description":"The filter isn't one Base44 can run, for example an unknown operator."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, 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 isn't a JSON object."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/entities/versions/info":{"get":{"summary":"Get data version history status","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns whether the app has version history for its entity data, and how far back it goes.\n\nBase44 records every change to an entity's records, and marks points you can go back to as [checkpoints](/api-reference/list-data-checkpoints). Read `state` before calling the other version history endpoints, since they're refused unless it's `enabled`.\n\nVersion history comes with the Elite plan and above. Elite keeps 7 days of history, and higher plans keep 30.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"get_app_version_history_info_api_apps__app_id__entities_versions_info_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":"The app's version history status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionHistoryInfo"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or your API key is read-only."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded. The base limit is 150 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/entities/versions/checkpoints":{"get":{"summary":"List data 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 points in the app's entity data history that you can download or restore to, newest first.\n\nBase44 adds one when an entity's records change (at most one every 5 minutes for each entity), when its schema changes, and just before a restore rewrites it. The list covers every entity in the app unless you name some with `entity_name`, and only reaches back as far as `retention_days` from [Get data version history status](/api-reference/get-data-version-history-status).\n\nResults use cursor-based pagination. Pass `next_cursor` from one response as `cursor` on the next request, and keep the same filters.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"list_app_version_checkpoints_api_apps__app_id__entities_versions_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":"entity_name","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Entity to list checkpoints for. Repeat it to list several, as in `?entity_name=Invoice&entity_name=Customer`, up to 200. Leave it out to list every entity's checkpoints. A name the app has no entity for matches nothing rather than failing.","title":"Entity Name"},"description":"Entity to list checkpoints for. Repeat it to list several, as in `?entity_name=Invoice&entity_name=Customer`, up to 200. Leave it out to list every entity's checkpoints. A name the app has no entity for matches nothing rather than failing.","example":["Invoice"]},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pagination cursor from previous response.","title":"Cursor"},"description":"Pagination cursor from previous response.","example":"gAAAAABn7vJ..."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","description":"Items per page. Max 200. Defaults to 50, and a larger value is treated as 200.","default":50,"title":"Limit"},"description":"Items per page. Max 200. Defaults to 50, and a larger value is treated as 200.","example":100},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Earliest `data_as_of` to include, as an ISO 8601 timestamp such as `2026-06-01T00:00:00Z`. A timestamp with no offset is read as UTC.","title":"From"},"description":"Earliest `data_as_of` to include, as an ISO 8601 timestamp such as `2026-06-01T00:00:00Z`. A timestamp with no offset is read as UTC.","example":"2026-06-01T00:00:00Z"},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only include checkpoints whose `data_as_of` is before this moment, as an ISO 8601 timestamp such as `2026-06-08T00:00:00Z`. A timestamp with no offset is read as UTC.","title":"To"},"description":"Only include checkpoints whose `data_as_of` is before this moment, as an ISO 8601 timestamp such as `2026-06-08T00:00:00Z`. A timestamp with no offset is read as UTC.","example":"2026-06-08T00:00:00Z"}],"responses":{"200":{"description":"One page of checkpoints.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckpointListPage"}}}},"400":{"description":"The cursor is malformed or was issued for different filters, `from` or `to` isn't an ISO 8601 timestamp, or you named more than 200 entities."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, your API key is read-only, or version history isn't on for the app. Read `state` from [Get data version history status](/api-reference/get-data-version-history-status) to see why."},"404":{"description":"App not found."},"422":{"description":"`limit` isn't a whole number."},"429":{"description":"Rate limit exceeded. The base limit is 70 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/entities/versions/restores/batch/{batch_id}":{"get":{"summary":"Get data restore status","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns where each entity in a restore has got to.\n\nPoll it after [Restore data to checkpoint](/api-reference/restore-data-to-checkpoint) until every entry in `operations` is `succeeded` or `failed`. Each entity finishes on its own, so one can fail while the rest succeed. A restore that stops making progress is marked `failed` rather than left running.\n\nAn ID the app has no restore for returns an empty `operations` list rather than an error. Base44 keeps a restore's status for 30 days.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"get_batch_restore_status_api_apps__app_id__entities_versions_restores_batch__batch_id__get","parameters":[{"name":"batch_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the restore, as `batch_id` from [Restore data to checkpoint](/api-reference/restore-data-to-checkpoint).","title":"Batch Id"},"description":"ID of the restore, as `batch_id` from [Restore data to checkpoint](/api-reference/restore-data-to-checkpoint).","example":"3f1c9a52-7b4e-4d2a-9c61-5e8f0b7a2d14"},{"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 restore's progress.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRestoreStatusResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or your API key is read-only."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded. The base limit is 150 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/entities/versions/checkpoints/{checkpoint_id}/restore":{"post":{"summary":"Restore data to checkpoint","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nStarts putting the entities you name back the way they were at a checkpoint, and returns the restore's ID.\n\nThe checkpoint marks a moment, so one checkpoint can restore any of the app's entities, not only the one it belongs to. For each entity, Base44 puts changed records back to their earlier values, brings back records deleted since, and moves records created since to the entity's trash.\n\nThe restore runs in the background, and a successful call only means it started. Poll [Get data restore status](/api-reference/get-data-restore-status) with `batch_id` until every entity's `status` is `succeeded` or `failed`. While an entity is being restored, writes to its records are refused. A restore doesn't trigger the app's webhooks, automations, or workflows.\n\nA restore can be undone. Base44 saves a backup of each entity just before rewriting it, and restoring the same entities from `undo_checkpoint_id` in the status response puts them back the way they were.\n\nAn entity that another restore is already running on is left out and listed in `skipped_entities`. The call is rejected when none of the entities changed after the checkpoint.\n\nThe call has no retry protection. A retry is refused while the first restore is running, and starts a second one after it finishes. If a response is lost, look for `restore` backups in [List data checkpoints](/api-reference/list-data-checkpoints) before you send it again.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"start_bulk_restore_api_apps__app_id__entities_versions_checkpoints__checkpoint_id__restore_post","parameters":[{"name":"checkpoint_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the checkpoint, as `checkpoint_id` from [List data checkpoints](/api-reference/list-data-checkpoints).","title":"Checkpoint Id"},"description":"ID of the checkpoint, as `checkpoint_id` from [List data checkpoints](/api-reference/list-data-checkpoints).","example":"68a1c2d3e4f5a6b7c8d9e0f1"},{"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/BulkRestoreRequest"}}}},"responses":{"202":{"description":"The restore started.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkRestoreStartResponse"}}}},"400":{"description":"`entity_names` is empty, names more than 300 entities or one the app doesn't have, or none of the entities changed after the checkpoint."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, your API key is read-only, or version history isn't on for the app. Read `state` from [Get data version history status](/api-reference/get-data-version-history-status) to see why."},"404":{"description":"App not found, or the app has no checkpoint with this ID that's still within the history."},"409":{"description":"A restore or a data import is already running in the app, or recent changes are still being saved to the history. Retry once it finishes."},"422":{"description":"The body is missing, or `entity_names` isn't a list of strings."},"429":{"description":"Rate limit exceeded. The base limit is 10 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/entities/{entity_name}/versions/checkpoints/{checkpoint_id}/snapshot":{"get":{"summary":"Download data 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 every record one entity held at a checkpoint, as it was just before the change that created the checkpoint.\n\nThe response is the entity's whole table in one piece, with no paging. `fields` lists its columns at the time, so you can write `records` out as a CSV file with the header the entity had then. Row-level security doesn't apply, and the records are always production data.\n\nDownloading changes nothing. To put the records back, use [Restore data to checkpoint](/api-reference/restore-data-to-checkpoint).\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"download_version_checkpoint_snapshot_api_apps__app_id__entities__entity_name__versions_checkpoints__checkpoint_id__snapshot_get","parameters":[{"name":"checkpoint_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the checkpoint, as `checkpoint_id` from [List data checkpoints](/api-reference/list-data-checkpoints).","title":"Checkpoint Id"},"description":"ID of the checkpoint, as `checkpoint_id` from [List data checkpoints](/api-reference/list-data-checkpoints).","example":"68a1c2d3e4f5a6b7c8d9e0f1"},{"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":"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. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","example":"Invoice"}],"responses":{"200":{"description":"The entity's records at the checkpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckpointSnapshotDownload"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, your API key is read-only, version history isn't on for the app, or the entity is `User`, which has no version history."},"404":{"description":"App not found, the app has no entity with this name, or the entity has no checkpoint with this ID that's still within the history."},"409":{"description":"Recent changes are still being saved to the history. Retry in a few seconds."},"429":{"description":"Rate limit exceeded. The base limit is 20 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/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, subject to row-level security.\n\nThe response carries the entity's own fields alongside the fields every record carries automatically. 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\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. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","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. The base limit is 150 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}},"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. Only the fields the entity's schema declares are stored, so a misspelled name is left out of the record instead of failing the call. Base44 always assigns `id`, `created_date`, `updated_date`, `created_by`, and `created_by_id` automatically, and ignores any of them you send.\n\nRow-level security applies, so the call is rejected when the entity's `rls` update rule doesn't cover this record. If field-level rules refuse a change to any field you're sending, the whole call is rejected too, and none of the fields update.\n\nUpdating a record triggers the app's webhooks and any automation or workflow that listens for this entity.\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. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","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":"On some apps, a field value over 20,000 characters is rejected. Store large content with the UploadPublicFile integration and use the returned URL instead."},"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, a required field the entity's schema declares is missing, or a value doesn't match the type the schema declares for that field."},"429":{"description":"Rate limit exceeded. The base limit is 70 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}},"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, subject to row-level security.\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 entity's trash rather than erasing it, so you can bring it back with [Restore entity record](/api-reference/restore-entity-record) or erase it with [Permanently delete entity record](/api-reference/permanently-delete-entity-record).\n\nDeleting a record triggers the app's webhooks and any automation or workflow that listens for this entity.\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. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","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. The base limit is 70 requests every 30 seconds. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/entities/{entity_name}/{entity_id}/restore":{"put":{"summary":"Restore 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\nBrings one deleted record back from the entity's trash and returns it.\n\nThe record keeps its ID and fields, and appears again in [List entity records](/api-reference/list-entity-records). Find the IDs of deleted records with [List deleted entity records](/api-reference/list-deleted-entity-records).\n\nRestoring a record notifies the entity's webhooks with a `restore` event, but only webhooks set to all events or to `restore`, and only for production data. It doesn't run the automations or workflows that listen for new or updated records.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"restore_entity_api_apps__app_id__entities__entity_name___entity_id__restore_put","parameters":[{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the deleted record, as `id` in the response of [List deleted entity records](/api-reference/list-deleted-entity-records).","title":"Entity Id"},"description":"ID of the deleted record, as `id` in the response of [List deleted entity records](/api-reference/list-deleted-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. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","example":"Invoice"}],"responses":{"200":{"description":"The restored 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 editor 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's trash holds no record with this ID."},"429":{"description":"Rate limit exceeded. The base limit is 70 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/entities/{entity_name}/{entity_id}/permanent":{"delete":{"summary":"Permanently 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\nErases one record from the entity's trash for good. It can't be restored afterwards.\n\nOnly a record already in the trash can be erased. Delete a live record with [Delete entity record](/api-reference/delete-entity-record) first. The record's earlier versions in the entity's version history aren't erased with it, and expire on the history's usual schedule.\n\nErasing a record notifies the entity's webhooks with a `permanent_delete` event, but only webhooks set to all events or to `permanent_delete`, and only for production data. It doesn't run the automations or workflows that listen for deleted records.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"delete_entity_permanently_api_apps__app_id__entities__entity_name___entity_id__permanent_delete","parameters":[{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the deleted record, as `id` in the response of [List deleted entity records](/api-reference/list-deleted-entity-records).","title":"Entity Id"},"description":"ID of the deleted record, as `id` in the response of [List deleted entity records](/api-reference/list-deleted-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. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","title":"Entity Name"},"description":"Name of the entity, exactly as [List entity schemas](/api-reference/list-entity-schemas) reports it. Don't pass `User` here. It doesn't fail, but it reads and writes a separate, disconnected set of records stored under that name, not the app's real user accounts, which are managed through their own endpoints.","example":"Invoice"}],"responses":{"200":{"description":"The record was erased.","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 editor 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's trash holds no record with this ID, including when the record is still live."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests every 30 seconds. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."}}}},"/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 403.","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 403.","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, the account belongs to a different app, 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 account with this ID in the workspace."},"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 403.","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 403.","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, the account belongs to a different app, 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 account with this ID in the workspace."},"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 403.","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 403.","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, the account belongs to a different app, 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 account with this ID in the workspace."}}}},"/api/apps/{app_id}/google-ads/billing/payment-setup":{"post":{"summary":"Create Google Ads payment setup","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 flow for saving a card against the workspace's ad spend, and returns the page a person completes.\n\nGoogle Ads spend is charged to a card on file. When [Get Google Ads workspace debt](/api-reference/get-google-ads-workspace-debt) reports `requires_payment_method` or `requires_payment_refresh`, this is what produces the link to fix it. Nothing is charged here, the session only validates and saves the card.\n\nSend the two URLs Stripe should return the person to. Both must be `https://` on a `base44.com` or `base44.app` host, so the return trip always lands back inside Base44 rather than on a site you name. A URL outside that set is rejected with a 400.\n\n<Note>Base44 never accepts card details through this API, and neither should you. This endpoint only produces the page a person fills in.</Note>\n\n<Note>Use [Create embedded Google Ads payment setup](/api-reference/create-embedded-google-ads-payment-setup) instead when you are rendering the card form inside your own page rather than sending someone to Stripe.</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":"create_payment_setup_api_apps__app_id__google_ads_billing_payment_setup_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":"CreatePaymentSetupSession","type":"object","required":["success_url","cancel_url"],"properties":{"success_url":{"type":"string","description":"Where Stripe sends the person after they save a card. It must be an `https://` URL on a `base44.com` or `base44.app` host, with no credentials in it, so you cannot send them back to your own site.","example":"https://app.base44.com/apps/6820f3a4e7b91d003c45a1f2/settings/billing?setup=done"},"cancel_url":{"type":"string","description":"Where Stripe sends the person if they abandon the form. Same host rules as `success_url`.","example":"https://app.base44.com/apps/6820f3a4e7b91d003c45a1f2/settings/billing"}}},"example":{"success_url":"https://app.base44.com/apps/6820f3a4e7b91d003c45a1f2/settings/billing?setup=done","cancel_url":"https://app.base44.com/apps/6820f3a4e7b91d003c45a1f2/settings/billing"}}}},"responses":{"200":{"description":"The page a person completes to save a card.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentSetupSession"}}}},"400":{"description":"`success_url` or `cancel_url` is not an `https://` URL, carries credentials, or is on a host outside `base44.com` and `base44.app`."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"422":{"description":"`success_url` or `cancel_url` is missing from the body. Both are required, and the check runs before the host rules above."}}}},"/api/apps/{app_id}/google-ads/accounts/{account_id}/billing/debt":{"get":{"summary":"Get Google Ads account 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 one of the app's Google Ads accounts owes for ad spend, and whether it can be settled.\n\nThis is the per-account view of [Get Google Ads workspace debt](/api-reference/get-google-ads-workspace-debt), with the same fields apart from `app_id`. Use that endpoint when you only know the workspace is on a hold. It finds the held account from any app, while this one only reads an account that belongs to the app in the path. Read `state` first. An account that is not held reports `not_recoverable`.\n\nNothing here charges anything. Settling a balance is a card transaction Base44 keeps out of this API, so treat a `payable` state as something to raise with whoever owns the card. Each call checks the card on file with the payment provider, and the first call for a workspace with no billing record there creates one.\n\nThis is limited to 30 requests a minute per app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key from anyone who can view the app. A read-only key is refused with a 403, because the card check can create the workspace's billing record. Workspace API keys are not authorized for it either 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_debt_recovery_summary_api_apps__app_id__google_ads_accounts__account_id__billing_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"},{"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 403.","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 403.","example":"68b1c0d4e7b91d003c45a1f8"}],"responses":{"200":{"description":"What the account owes, and whether it can be settled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsAccountDebtSummary"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't view this app, the app does not exist, the account belongs to a different app, or you used a read-only or workspace API key."},"404":{"description":"There is no Google Ads account with this ID in the workspace."},"429":{"description":"The app has used up its 30 debt reads for the current minute. Retry later."}}}},"/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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."}}}},"/api/apps/{app_id}/google-ads/billing/workspace-pay-now":{"post":{"summary":"Pay 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\nCharges the workspace's card on file to settle its outstanding Google Ads balance.\n\n<Warning>This moves money. It charges the card immediately, with no confirmation step and nothing in the request that asks a person first, so treat calling it as the approval. Read [Get Google Ads workspace debt](/api-reference/get-google-ads-workspace-debt) beforehand to see the amount, and only call this when a human has agreed to it.</Warning>\n\nRead `status` rather than the HTTP status. Only `paid`, `already_settled` and `paid_recovery_incomplete` mean the balance is dealt with; every other value comes back under a 200 having charged nothing, and `summary` tells you why.\n\nRetrying is safe. Base44 serializes the charge per account against the specific invoice, so two calls racing each other cannot double-charge, and a repeat call once a charge is in flight reports `pending_payment` instead of starting another.\n\nThe body takes no fields. Send an empty object, since anything else is rejected.\n\nSettling clears the hold that stops campaigns serving, so a campaign paused for non-payment can resume afterwards. The `summary` in the response reports whether the account actually came back, which is what `paid_recovery_incomplete` distinguishes.\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":"pay_now_workspace_debt_api_apps__app_id__google_ads_billing_workspace_pay_now_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":"PayWorkspaceDebt","type":"object","properties":{}},"example":{}}}},"responses":{"200":{"description":"What happened, and the balance afterwards.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayNowResult"}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The workspace has no usable card on file, so there is nothing to charge. Save one with [Create Google Ads payment setup](/api-reference/create-google-ads-payment-setup) first."},"403":{"description":"You don't have access to this app, the app does not exist, you used a workspace API key, or the workspace has an open payment dispute, which blocks any new charge until support resolves it."},"404":{"description":"The workspace has no outstanding Google Ads balance, so there is nothing to settle. This is a normal state, not an error."},"409":{"description":"The workspace's Google Ads account is not in a state Base44 can settle through this endpoint."},"503":{"description":"Stripe is unreachable, or a charge on this account is already running and held the lock. Neither charged anything on this call, and both are temporary, so retry shortly."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/billing/payment-setup-embedded":{"post":{"summary":"Create embedded Google Ads payment setup","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 card-saving flow for rendering inside your own page, rather than sending a person to a Stripe-hosted page.\n\nThis is the embedded counterpart to [Create Google Ads payment setup](/api-reference/create-google-ads-payment-setup). It takes no body, because there is no return trip to configure: you mount Stripe's embedded checkout with the `client_secret` and poll [Get Google Ads billing status](/api-reference/get-google-ads-billing-status) to see when the card lands. Nothing is charged here.\n\nPrefer the hosted flow unless you are actually building the form. It hands you one URL to give a person, where this hands you a token you still have to render Stripe's component around.\n\n<Note>Base44 never accepts card details through this API, and neither should you. This endpoint only produces the page a person fills in.</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":"create_embedded_payment_setup_api_apps__app_id__google_ads_billing_payment_setup_embedded_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":"The token for mounting Stripe's embedded checkout.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbeddedPaymentSetupSession"}}}},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."}}}},"/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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."}}}},"/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 403.","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 403.","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, the account belongs to a different app, 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 account with this ID in the workspace."},"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 403.","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 403.","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, the account belongs to a different app, 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 account with this ID in the workspace."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."}}}},"/api/apps/{app_id}/google-ads/campaign-launch-drafts/{draft_id}":{"get":{"summary":"Get Google Ads campaign launch draft","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 Performance Max campaign the builder chat prepared for someone to review before launch.\n\nThe chat saves a draft when you ask it to set up a campaign and choose a budget. It holds the copy, pictures, budget and locations as the chat left them. Nothing in it is checked against Google until launch, so a draft can be incomplete: a missing logo or landing page is common. Review it, fill in what is missing, and use its fields to build the request for [Launch Google Ads campaign](/api-reference/launch-google-ads-campaign).\n\n`launched_at` only records a launch from the draft's review page in the builder. Launching through the API doesn't mark the draft, so `launched_at` stays `null` and nothing stops the same draft from being launched again. Keep track of which drafts you have launched yourself.\n\nOnly a draft for the app in the path is returned. Any other ID returns a 404.\n\nThis is limited to 120 requests a minute per app, shared with [List Google Ads advertising languages](/api-reference/list-google-ads-advertising-languages). Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key from anyone who can view the app, including a read-only 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_launch_draft_api_apps__app_id__google_ads_campaign_launch_drafts__draft_id__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"},{"name":"draft_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the draft. The builder chat returns it as `draft_id` when it saves a launch draft, and its review link carries it as `?draft=`.","title":"Draft Id"},"description":"ID of the draft. The builder chat returns it as `draft_id` when it saves a launch draft, and its review link carries it as `?draft=`.","example":"68c2d1e5f8a92e004d56b2a3"}],"responses":{"200":{"description":"The draft as the builder chat saved it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsCampaignLaunchDraft"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't view 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 launch draft with this ID for this app."},"429":{"description":"The app has used up the 120 requests a minute it shares with List Google Ads advertising languages. Retry later."}}}},"/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 these from the request before creating anything, and a request that fails one creates nothing:\n\n- A Performance Max campaign has a `logo_url`, plus at least one `MARKETING_IMAGE` and one `SQUARE_MARKETING_IMAGE` whose `url` is on a host Base44 fetches images from.\n- The budget sits inside the per-currency range for the campaign type.\n- The business name is within its length limit.\n- `sitelinks` and `phone_number` are well formed.\n- Targeting that can reach the EU carries a political-advertising declaration.\n\nAnything Base44 only finds out by fetching, or that only Google can judge, fails after the account exists. That includes an image or logo that doesn't download and a landing page Google can't crawl.\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 the logo, at least 128×128. Required for a `PERFORMANCE_MAX` campaign and ignored for `SMART`. It follows the same hosting rules as `images[].url`, but unlike an image a logo Base44 cannot fetch fails the request instead of being left out. A logo that is not square is padded to square."},"images":{"type":"array","items":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Address of the image. It has to be `https` on a host Base44 fetches images from, such as an address on `base44.com` or `base44.app`. Use a static PNG, JPEG, or GIF of 5 MB or less.","example":"https://storage.base44.com/gads-assets/b7f3a1c8-landscape.png"},"field_type":{"type":"string","enum":["MARKETING_IMAGE","SQUARE_MARKETING_IMAGE","PORTRAIT_MARKETING_IMAGE","TALL_PORTRAIT_MARKETING_IMAGE"],"default":"MARKETING_IMAGE","description":"Which slot the image fills. Base44 does not crop or resize, so the image has to match its slot's shape to within 1% already, or Google rejects it:\n- `MARKETING_IMAGE` is 1.91:1 landscape, at least 600×314.\n- `SQUARE_MARKETING_IMAGE` is 1:1, at least 300×300.\n- `PORTRAIT_MARKETING_IMAGE` is 4:5, at least 480×600.\n- `TALL_PORTRAIT_MARKETING_IMAGE` is 9:16, at least 600×1067.\n\nDefaults to `MARKETING_IMAGE`, and a value outside this list is treated as `MARKETING_IMAGE` too.","example":"MARKETING_IMAGE"},"name":{"type":"string","description":"Label for the image in Google Ads. Defaults to `image`.","example":"Showroom landscape"}}},"description":"Marketing images for a `PERFORMANCE_MAX` campaign, ignored for `SMART`. A Performance Max campaign needs at least one `MARKETING_IMAGE` and one `SQUARE_MARKETING_IMAGE`, and Google rejects the campaign without them.\n\nBase44 leaves out an image it cannot fetch, rather than rejecting the request. That includes an address on another host, a file over 5 MB, and one that does not respond. If that leaves a required slot empty, Google rejects the campaign for the missing image. An address that returns something other than an image is rejected outright."},"sitelinks":{"type":"array","items":{"type":"object","required":["text","url"],"properties":{"text":{"type":"string","description":"Link text. Google shows at most 25 characters, and Base44 cuts longer text down to 25 rather than rejecting it.","example":"Visit our showroom"},"url":{"type":"string","description":"Where the link goes, as an `http` or `https` address.","example":"https://example.com/showroom"}}},"description":"Extra links shown under a `PERFORMANCE_MAX` campaign's ads, up to 20. Ignored for `SMART`. A row with neither `text` nor `url` is dropped, a row with only one of them is rejected, and exact duplicates collapse to one."},"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 on SMART it becomes `smartCampaignSettings.phone_number`; either way it 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. 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`."}}},"examples":{"smart":{"summary":"Launch a Smart campaign","value":{"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"}},"performance_max":{"summary":"Launch a Performance Max campaign","value":{"campaign":{"campaign_name":"Spring sale","campaign_type":"PERFORMANCE_MAX","daily_budget_micros":15000000,"landing_page":"https://example.com","business_name":"Nordwind Furniture","headlines":["Oak dining tables","Spring sale on now","Made in Berlin"],"descriptions":["Solid oak, built to order.","Free delivery across Germany this spring."],"logo_url":"https://storage.base44.com/gads-assets/b7f3a1c8-logo.png","images":[{"url":"https://storage.base44.com/gads-assets/b7f3a1c8-landscape.png","field_type":"MARKETING_IMAGE"},{"url":"https://storage.base44.com/gads-assets/b7f3a1c8-square.png","field_type":"SQUARE_MARKETING_IMAGE"}],"sitelinks":[{"text":"Visit our showroom","url":"https://example.com/showroom"}]},"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. That covers a Performance Max campaign with no `logo_url` or without its landscape and square images, malformed `sitelinks` or `phone_number`, 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, and a rejection passed through from Google Ads. Missing images come back in the standard error envelope, with `error.code` set to `performance_max_images_missing` and the empty slots in `error.details.missing_image_field_types`."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"404":{"description":"The `draft_id` does not name a launch draft for this app."},"409":{"description":"One of seven things: the workspace is on billing hold, the account is, this draft was already launched, the request to Google Ads timed out after being sent and the campaign may be live, another campaign is being created on this account right now (`error.code` `campaign_create_in_progress`; list the campaigns and retry only if the one you meant is not there, since a retry after it lands creates a second campaign), a campaign with this name exists in Google Ads but hasn't synced here (`error.code` `campaign_name_exists_on_google`; do not retry), or a previous Smart campaign didn't finish setting up (`error.code` `incomplete_campaign_exists`; delete it first). The message says which; the timeout and the existing name 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 the logo, at least 128×128. Required for a `PERFORMANCE_MAX` campaign and ignored for `SMART`. It follows the same hosting rules as `images[].url`, but unlike an image a logo Base44 cannot fetch fails the request instead of being left out. A logo that is not square is padded to square."},"images":{"type":"array","items":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Address of the image. It has to be `https` on a host Base44 fetches images from, such as an address on `base44.com` or `base44.app`. Use a static PNG, JPEG, or GIF of 5 MB or less.","example":"https://storage.base44.com/gads-assets/b7f3a1c8-landscape.png"},"field_type":{"type":"string","enum":["MARKETING_IMAGE","SQUARE_MARKETING_IMAGE","PORTRAIT_MARKETING_IMAGE","TALL_PORTRAIT_MARKETING_IMAGE"],"default":"MARKETING_IMAGE","description":"Which slot the image fills. Base44 does not crop or resize, so the image has to match its slot's shape to within 1% already, or Google rejects it:\n- `MARKETING_IMAGE` is 1.91:1 landscape, at least 600×314.\n- `SQUARE_MARKETING_IMAGE` is 1:1, at least 300×300.\n- `PORTRAIT_MARKETING_IMAGE` is 4:5, at least 480×600.\n- `TALL_PORTRAIT_MARKETING_IMAGE` is 9:16, at least 600×1067.\n\nDefaults to `MARKETING_IMAGE`, and a value outside this list is treated as `MARKETING_IMAGE` too.","example":"MARKETING_IMAGE"},"name":{"type":"string","description":"Label for the image in Google Ads. Defaults to `image`.","example":"Showroom landscape"}}},"description":"Marketing images for a `PERFORMANCE_MAX` campaign, ignored for `SMART`. A Performance Max campaign needs at least one `MARKETING_IMAGE` and one `SQUARE_MARKETING_IMAGE`, and Google rejects the campaign without them.\n\nBase44 leaves out an image it cannot fetch, rather than rejecting the request. That includes an address on another host, a file over 5 MB, and one that does not respond. If that leaves a required slot empty, Google rejects the campaign for the missing image. An address that returns something other than an image is rejected outright."},"sitelinks":{"type":"array","items":{"type":"object","required":["text","url"],"properties":{"text":{"type":"string","description":"Link text. Google shows at most 25 characters, and Base44 cuts longer text down to 25 rather than rejecting it.","example":"Visit our showroom"},"url":{"type":"string","description":"Where the link goes, as an `http` or `https` address.","example":"https://example.com/showroom"}}},"description":"Extra links shown under a `PERFORMANCE_MAX` campaign's ads, up to 20. Ignored for `SMART`. A row with neither `text` nor `url` is dropped, a row with only one of them is rejected, and exact duplicates collapse to one."},"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 on SMART it becomes `smartCampaignSettings.phone_number`; either way it 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. 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 and the campaign may be live (read the campaigns back before retrying), another campaign is being created on this account right now (`error.code` `campaign_create_in_progress`; list the campaigns and retry only if the one you meant is not there, since a retry after it lands creates a second campaign), a campaign with this name exists in Google Ads but hasn't synced here (`error.code` `campaign_name_exists_on_google`; do not retry), or a previous Smart campaign didn't finish setting up (`error.code` `incomplete_campaign_exists`; delete it first)."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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/sync":{"post":{"summary":"Sync 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\nReads the account's campaigns from Google Ads now, saves them to Base44, and returns what Google reported.\n\nFor each campaign it reads, Base44 replaces its own record of the name, status, type, daily budget, and serving and policy status with Google's. A campaign on the account that Base44 has no record of, such as one created outside Base44, is added. Deleted campaigns are skipped.\n\nBase44 already refreshes campaign statuses and metrics from Google in the background about once an hour, and that refresh never adds a campaign Base44 doesn't know about. Call this when you need Google's current state right away, or to bring in campaigns created outside Base44.\n\nThe rows carry `google_campaign_id` but not Base44's `id`. To act on a campaign afterwards, find its `id` in [List campaigns](/api-reference/list-google-ads-campaigns). The metrics are not limited to a date range. Use [Get campaign metrics](/api-reference/get-google-ads-campaign-metrics) for figures over a window.\n\n<Warning>A sync reads at most 50 campaigns, the ones with the most impressions, and there is no pagination. On an account with more campaigns than that, the rest are neither refreshed nor returned, and nothing in the response says so.</Warning>\n\nThis keeps working while the workspace is on a billing hold, so the campaigns it owes for can still be refreshed.\n\nThis is limited to 6 requests a minute per app. Some workspaces have a different limit.\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":"sync_campaigns_api_apps__app_id__google_ads_campaigns_sync_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":"The campaigns Google reported, as Base44 saved them.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SyncedCampaign"},"title":"Response 200 Sync Campaigns Api Apps  App Id  Google Ads Campaigns Sync Post"}}}},"400":{"description":"Google Ads rejected the read."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, you used a read-only or workspace API key, or Google Ads refused access to the account. A missing app and an app you cannot reach are deliberately the same answer."},"404":{"description":"The app has no connected Google Ads account."},"429":{"description":"The app has used up its sync quota for the current minute, or Google Ads is rate limiting the account. Retry later."}}}},"/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\n`clicks_per_unit_min` and `clicks_per_unit_max` are always present, and are measured rather than forecast whenever `clicks_per_unit_scope` is `campaign` or `fleet`: the middle half of what one unit of `currency_code` in daily budget has actually bought across recent Base44 campaigns billing in that currency. Multiply a daily budget by them directly. Pass `campaign_id` to get the same pair for one campaign's own delivery once it has enough of a record to speak for itself. A scope of `unavailable` still returns a usable pair, but a stored one rather than a current measurement — read the scope before presenting these as live figures.\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"},{"name":"campaign_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"An existing campaign to quote the click rate for. With it, `clicks_per_unit_min` / `clicks_per_unit_max` describe that campaign's own recent delivery instead of the platform-wide range, and `clicks_per_unit_scope` reads `campaign`. A campaign without enough delivery, or one whose budget currency differs from `currency_code`, still answers from the platform-wide range.","title":"Campaign Id"},"description":"An existing campaign to quote the click rate for. With it, `clicks_per_unit_min` / `clicks_per_unit_max` describe that campaign's own recent delivery instead of the platform-wide range, and `clicks_per_unit_scope` reads `campaign`. A campaign without enough delivery, or one whose budget currency differs from `currency_code`, still answers from the platform-wide range."}],"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."}}}},"/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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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}/impact":{"get":{"summary":"Get Google Ads campaign conversion breakdown","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nBreaks one campaign's conversions down by goal, by the Google surface they came through, and by where the people who converted were, for a date window.\n\nBase44 reads all three live from Google Ads as separate reads, and each one can fail on its own. A failed read still returns a 200: its `*_unavailable` flag is `true` and its lists are empty, so show that section as unavailable rather than as zero conversions.\n\n- **Goals.** `goals` totals conversions and conversion value for the `sales`, `leads`, `shopping` and `other` families, and `goal_actions` lists the conversion categories behind them. A category whose conversions round to zero is left out of `goal_actions` but still counts in `goals`, so a total can differ slightly from the sum of its rows. `BEGIN_CHECKOUT` and `ADD_TO_CART` count as `shopping`, not `sales`, and can carry a value, so don't add `shopping` to a sales total as revenue.\n- **Channels.** `channels` ranks conversions by ad network. Conversions Google reports with no resolved network, which includes every Performance Max date before 1 June 2025, are summed into `channels_unresolved` instead.\n- **Locations.** `countries` and `locations` rank conversions by where people were, not by what they searched for. Conversions Base44 can't name a place for are summed into `countries_unresolved` and `locations_unresolved`.\n\nThe ranked lists leave out entries that round to zero. `goals_configured` names the goal families the app reports conversions for, so you can hide one it never tracks.\n\n`start_date` and `end_date` are inclusive, must both be `YYYY-MM-DD`, and are read in the Google Ads account's time zone. Anything else is rejected with a 400. Keep `start_date` on or before `end_date`. The campaign has to have reached Google Ads, so one with no `google_campaign_id` yet returns a 404.\n\nThis is limited to 30 requests a minute per app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key from anyone who can view the app, including a read-only key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"get_campaign_impact_api_apps__app_id__google_ads_campaigns__campaign_id__impact_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":"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":"start_date","in":"query","required":true,"schema":{"type":"string","description":"First day of the window, as `YYYY-MM-DD`.","title":"Start Date"},"description":"First day of the window, as `YYYY-MM-DD`.","example":"2026-09-01"},{"name":"end_date","in":"query","required":true,"schema":{"type":"string","description":"Last day of the window, inclusive, as `YYYY-MM-DD`.","title":"End Date"},"description":"Last day of the window, inclusive, as `YYYY-MM-DD`.","example":"2026-09-30"}],"responses":{"200":{"description":"The campaign's conversions by goal, channel and location.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsCampaignImpact"}}}},"400":{"description":"`start_date` or `end_date` is not a valid `YYYY-MM-DD` date."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't view 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, there is no campaign with this ID, or the campaign has not reached Google Ads yet."},"429":{"description":"The app has used up its 30 breakdowns for the current minute. Retry later."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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","add_video","videos","products"],"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. `add_video` takes `{\"youtube_video\": \"<link or video id>\"}` and links a NEW video, where `asset` replaces an existing one. `videos` takes `{\"youtube_videos\": [\"<link or video id>\", ...]}` — the COMPLETE desired set, so links absent from it are removed. `products` (Shopping only) takes `{\"excluded_product_ids\": [\"<item id>\", ...]}` — the COMPLETE desired excluded set, so ids absent from it are advertised again."}}},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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 403.","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 403.","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, the account belongs to a different app, 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 account with this ID in the workspace."}}}},"/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 403.","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 403.","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, the account belongs to a different app, 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 account with this ID in the workspace."}}}},"/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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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, as they would go to [Create campaign](/api-reference/create-google-ads-campaign). Nothing is saved.\n\nGoogle's planner picks the budget it forecasts at — it does not take one — so the figures come back for Google's own recommended daily budget, which `estimated_monthly_cost` reports.\n\nGoogle forecasts for a landing page in specific places, so a request without `landing_page` or without at least one `geo_targets` entry is rejected with a 400 before it reaches Google.\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>Each call asks Google's planner, so this endpoint has its own quota of 10 requests per minute per app — separate from the AI pool. A 429 means that quota is spent; retry in a minute.</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":"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 locations you sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsBudgetEstimate"}}}},"400":{"description":"The Google Ads account has not finished provisioning, or the request has no landing page / target locations."},"429":{"description":"This endpoint's own forecast 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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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/cpc-estimate":{"post":{"summary":"Estimate Google Ads cost per click","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nEstimates what one click costs for a landing page in a set of locations, so you can turn a daily budget into a click range before a campaign exists.\n\nGoogle publishes no click forecast for a Performance Max campaign before it launches, so this answers a narrower question. Base44 asks Google's keyword planner for the keyword ideas it derives from `landing_page`, averages the top-of-page bid range over up to 10 of them, and takes 60% of that range, because a click usually costs less than the top-of-page bid. Divide a daily budget by `cpc_high_micros` and `cpc_low_micros` for the click range. Both are in the account's budget currency, so no conversion is needed. To forecast a Smart campaign from its keyword themes instead, use [Estimate a Google Ads campaign budget](/api-reference/estimate-a-google-ads-campaign-budget).\n\nA `sample_size` of `0`, with both figures `0`, means Google had no keyword ideas it could price. Treat that as no estimate, not as free clicks.\n\nThe app needs a connected Google Ads account that has finished provisioning. An account with no Google customer ID yet, and a `landing_page` that is not an `http` or `https` URL, are rejected with a 400 before anything reaches Google. Nothing is saved and nothing is spent on ads.\n\nThis is limited to 30 requests a minute per app. Some workspaces have a different limit. It is refused with a 409 while the workspace is on a Google Ads billing hold.\n\n<Note>This endpoint accepts a personal API key from anyone who can edit the app. A read-only key is refused with a 403, and so are workspace API keys.</Note>","operationId":"estimate_campaign_cpc_api_apps__app_id__google_ads_campaigns_cpc_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/CpcEstimateRequest"}}}},"responses":{"200":{"description":"The estimated cost of one click.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsCpcEstimate"}}}},"400":{"description":"`landing_page` is not an `http` or `https` URL, the account has not finished provisioning, or Google Ads rejected the request."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't edit this app, the app does not exist, or you used a read-only or 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 workspace is on a Google Ads billing hold."},"429":{"description":"The app has used up its 30 estimates for the current minute, or 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/suggestions":{"post":{"summary":"Suggest Google Ads budget options","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's Smart campaign planner for a low, a recommended and a high daily budget for a set of keyword themes, each with its daily click forecast.\n\nThe response is Google's own answer, passed through as Google sends it. That is why its fields are camelCase and its numbers are strings, unlike the rest of this API, and why a value of `0` is left out rather than sent. Any tier can be missing. [Estimate a Google Ads campaign budget](/api-reference/estimate-a-google-ads-campaign-budget) makes the same planner call and returns one recommended forecast in this API's usual shape, so prefer it unless you need all three tiers.\n\nGoogle needs a landing page and at least one location, so a request without an `http` or `https` `landing_page` or without a usable `geo_targets` entry is rejected with a 400 before it reaches Google. Entries in `geo_targets` that are not geo target constant IDs are dropped. Nothing is saved.\n\nThe app needs a connected Google Ads account that has finished provisioning. Check that `google_customer_id` in [Get Google Ads account](/api-reference/get-google-ads-account) is not empty before you call this.\n\nThis shares a quota of 10 requests a minute per app with [Estimate a Google Ads campaign budget](/api-reference/estimate-a-google-ads-campaign-budget). Some workspaces have a different limit. It is refused with a 409 while the workspace is on a Google Ads billing hold.\n\n<Note>This endpoint accepts a personal API key from anyone who can edit the app. A read-only key is refused with a 403, and so are workspace API keys.</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":"get_suggestions_api_apps__app_id__google_ads_campaigns_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/SuggestionsRequest"}}}},"responses":{"200":{"description":"Google's budget options, as Google sent them.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsBudgetOptions"}}}},"400":{"description":"The request has no `http` or `https` `landing_page` or no usable `geo_targets` entry, or Google Ads rejected the request."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't edit this app, the app does not exist, or you used a read-only or 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 workspace is on a Google Ads billing hold."},"429":{"description":"The app has used up the 10 planner requests a minute it shares with Estimate a Google Ads campaign budget, or 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/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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."}}}},"/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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/google-ads/setup-suggestions":{"post":{"summary":"Suggest Google Ads setup targeting","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSuggests where to advertise, which keyword themes to target, and which conversions to track for the app's first campaign, read from the app itself.\n\nA language model reads the app's name, description, pages, data entities and page source once, then Base44 resolves the places it names to real Google locations. Everything here is a suggestion for someone to confirm. Nothing is saved, and the app does not need a Google Ads account yet.\n\nThe call never fails on the suggestions themselves. When the app has nothing to read, the model fails or answers with nothing usable, or Google can't resolve a place, the affected lists come back empty under a 200, so an empty list means \"no suggestion\" rather than \"none apply\". `conversion_categories` in particular is empty unless the app already has the flow behind a goal, such as a booking page or a checkout. Expect it to take 30 seconds or more.\n\nOnce the app has a provisioned Google Ads account, [Suggest Google Ads setup keyword themes](/api-reference/suggest-google-ads-setup-keyword-themes) gets Google's own themes for the locations you settle on. Use those first and fill up from `keyword_themes`.\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\nIt is refused with a 409 while the workspace is on a Google Ads billing hold. Running the model does not spend credits.\n\n<Note>This endpoint accepts a personal API key from anyone who can edit the app. A read-only key is refused with a 403, and so are workspace API keys.</Note>","operationId":"get_setup_suggestions_api_apps__app_id__google_ads_setup_suggestions_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app you are setting up Google Ads for.","title":"App Id"},"description":"ID of the app you are setting up Google Ads for.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Suggested locations, keyword themes and conversion goals.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsSetupSuggestions"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't edit this app, the app does not exist, or you used a read-only or workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"409":{"description":"The workspace is on a Google Ads billing hold."},"429":{"description":"The app has used up its 20 language-model requests a minute. Retry later."}}}},"/api/apps/{app_id}/google-ads/setup-suggestions/keyword-themes":{"post":{"summary":"Suggest Google Ads setup 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\nAsks Google which keyword themes fit the app's landing page in the locations you picked, for a campaign you have not launched yet.\n\nThese come from Google's own read of the app's published site, so prefer them over the generated `keyword_themes` from [Suggest Google Ads setup targeting](/api-reference/suggest-google-ads-setup-targeting) and fill up from those. Send the `language_code` that endpoint returned, so Google answers in the same language.\n\nThe call always returns a 200 once it is allowed through. `keyword_themes` is empty when Google cannot answer: the app has no Google Ads account with a Google customer ID yet, the app is not published where Google can read it, none of `geo_targets` is a geo target constant ID, Google returns nothing, or the request to Google fails. Read an empty list as \"keep the generated themes\", not as an error. Nothing is saved, and no language model runs.\n\nThis is limited to 10 requests a minute per app. Some workspaces have a different limit. It is refused with a 409 while the workspace is on a Google Ads billing hold.\n\n<Note>This endpoint accepts a personal API key from anyone who can edit the app. A read-only key is refused with a 403, and so are workspace API keys.</Note>","operationId":"get_setup_keyword_themes_api_apps__app_id__google_ads_setup_suggestions_keyword_themes_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app you are setting up Google Ads for.","title":"App Id"},"description":"ID of the app you are setting up Google Ads for.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsSetupKeywordThemesRequest"}}}},"responses":{"200":{"description":"Google's keyword themes, or an empty list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsSetupKeywordThemes"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't edit this app, the app does not exist, or you used a read-only or workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"409":{"description":"The workspace is on a Google Ads billing hold."},"422":{"description":"The body has a field other than `geo_targets` and `language_code`, or a field of the wrong type."},"429":{"description":"The app has used up its 10 keyword theme requests for the current minute. Retry later."}}}},"/api/apps/{app_id}/google-ads/setup-suggestions/regenerate-keyword":{"post":{"summary":"Regenerate a Google Ads setup keyword theme","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSuggests one new keyword theme to replace one you rejected, for a campaign you have not launched yet.\n\nSend the theme to replace as `current` and every theme already in the campaign as `existing`. A language model writes one replacement from the app's own content, and Base44 checks that it differs from all of them, ignoring case.\n\nThe call returns a 200 with `keyword_theme` set to `null` when there is no new theme: the app has nothing to read, the model fails, or it only comes up with themes you already have. Keep the old theme then. Nothing is saved.\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\nIt is refused with a 409 while the workspace is on a Google Ads billing hold. Running the model does not spend credits.\n\n<Note>This endpoint accepts a personal API key from anyone who can edit the app. A read-only key is refused with a 403, and so are workspace API keys.</Note>","operationId":"regenerate_setup_keyword_api_apps__app_id__google_ads_setup_suggestions_regenerate_keyword_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app you are setting up Google Ads for.","title":"App Id"},"description":"ID of the app you are setting up Google Ads for.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsRegenerateKeywordRequest"}}}},"responses":{"200":{"description":"One replacement theme, or `null`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsRegeneratedKeywordTheme"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't edit this app, the app does not exist, or you used a read-only or workspace API key. A missing app and an app you cannot reach are deliberately the same answer."},"409":{"description":"The workspace is on a Google Ads billing hold."},"422":{"description":"The body has a field other than `current` and `existing`, or a field of the wrong type."},"429":{"description":"The app has used up its 20 language-model requests a minute. Retry later."}}}},"/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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."}}}},"/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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Fill both in before you send that payload, hosted where the `images` field of [Create campaign](/api-reference/create-google-ads-campaign) says Base44 can fetch them.\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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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/incentives":{"get":{"summary":"List Google Ads incentives","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 offers Google Ads has for the app's account. Each one grants ad credit once the account's spend reaches a set amount.\n\nThe offers are personal to you. Google picks them from your email address, so another member of the workspace can see different ones.\n\nPass `account_id` once the app has a Google Ads account, so the offers are quoted in its currency. Before then, leave it out and the offers are quoted in the currency a new account would be created in, which depends on where the request comes from. If the account ends up in a different currency, applying an offer read that way is rejected.\n\nAn empty list is a normal answer. It comes back when the account already holds an incentive, when incentives aren't offered in the account's currency, which today means anything other than `USD` or `ILS`, and when Google has nothing for you.\n\nTake an offer with [Apply a Google Ads incentive](/api-reference/apply-a-google-ads-incentive).\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_incentives_api_apps__app_id__google_ads_incentives_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":"account_id","in":"query","required":false,"schema":{"type":"string","description":"ID of the app's Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). Leave it out before the app has an account. Defaults to empty, which quotes the offers for a new account.","default":"","title":"Account Id"},"description":"ID of the app's Google Ads account, as returned in `id` by [Get Google Ads account](/api-reference/get-google-ads-account). Leave it out before the app has an account. Defaults to empty, which quotes the offers for a new account.","example":"68b1c0d4e7b91d003c45a1f8"}],"responses":{"200":{"description":"The offers you can apply, or an empty list when there are none.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GoogleAdsIncentiveOffer"},"title":"Response 200 List Incentives Api Apps  App Id  Google Ads Incentives Get"}}}},"400":{"description":"Google Ads rejected the request. The response message carries Google's reason."},"401":{"description":"Missing or invalid credentials, or the calling user has no email address to look offers up by."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"404":{"description":"No account with this `account_id` belongs to this app."},"429":{"description":"Google Ads is rate limiting the account. Retry later."}}}},"/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 403.","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 403.","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, the account belongs to a different app, 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 account with this ID in the workspace."},"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 403.","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 403.","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, the account belongs to a different app, 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 account with this ID in the workspace."}}}},"/api/apps/{app_id}/google-ads/accounts/{account_id}/incentives/apply":{"post":{"summary":"Apply a Google Ads incentive","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nApplies one of the offers from [List Google Ads incentives](/api-reference/list-google-ads-incentives) to the app's Google Ads account.\n\nSend the offer's `code` as `coupon_code`. Google then tracks the account's spend against the offer, and once the spend requirement is met it grants the reward as ad credit, which appears in [List Google Ads promotional credits](/api-reference/list-google-ads-promotional-credits).\n\nAn account holds one incentive at most. When it already holds one, nothing is applied and the response carries the code of the incentive it holds. Compare `applied_incentive_code` with what you expected rather than assuming a successful call applied your offer.\n\nApplying the same offer again doesn't apply it twice. While an earlier attempt is still settling, a new call is rejected without reaching Google, so wait a few minutes and retry.\n\nThe account has to be provisioned in Google Ads, and the offer has to be quoted in the account's currency, so read the offers with `account_id` set. Applying isn't available while the workspace is on a billing hold.\n\n<Note>Each call counts against a per-app quota, separate from the other Google Ads quotas. The default is 5 requests a minute, and the workspace's plan can raise it, so treat a rejection rather than a fixed count as the signal to back off.</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":"apply_incentive_api_apps__app_id__google_ads_accounts__account_id__incentives_apply_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 403.","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 403.","example":"68b1c0d4e7b91d003c45a1f8"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"ApplyGoogleAdsIncentive","type":"object","required":["coupon_code"],"properties":{"coupon_code":{"type":"string","description":"The `code` of the offer to apply, as returned by [List Google Ads incentives](/api-reference/list-google-ads-incentives).","example":"SELECTED:1234567:USD"}}},"example":{"coupon_code":"SELECTED:1234567:USD"}}}},"responses":{"200":{"description":"The incentive the account holds after the call.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppliedIncentiveResult"}}}},"400":{"description":"`coupon_code` is empty, or Google Ads rejected the offer. 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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"404":{"description":"The app has no connected Google Ads account, or there is no account with this ID in the workspace."},"409":{"description":"The offer wasn't applied. The account isn't provisioned in Google Ads yet, incentives aren't offered in its currency, the offer was quoted in a different currency, another apply on the account is still settling, or the workspace is on a billing hold. The request to Google Ads timing out after being sent also reports 409, and then the offer may or may not have been applied, so wait a few minutes and retry. The retry checks with Google first."},"422":{"description":"The body is missing, or it has no `coupon_code` string."},"429":{"description":"The app has used up its incentive-apply quota for the current minute, or Google Ads is rate limiting the account. Retry later."}}}},"/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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"404":{"description":"The app has no connected Google Ads account, or there is no campaign with this ID."}}}},"/api/apps/{app_id}/google-ads/languages":{"get":{"summary":"List Google Ads advertising languages","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 languages a campaign's ads can run in.\n\nThese are the codes the rest of the Google Ads endpoints accept as an ads language, for example `language_code` on [Estimate Google Ads cost per click](/api-reference/estimate-google-ads-cost-per-click). The list is the same for every app and changes only when Base44 adds a language, so you can cache it. The app does not need a Google Ads account.\n\nThis is limited to 120 requests a minute per app, shared with [Get Google Ads campaign launch draft](/api-reference/get-google-ads-campaign-launch-draft). Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key from anyone who can view the app, including a read-only key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>","operationId":"list_ad_languages_api_apps__app_id__google_ads_languages_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":"The supported ads languages.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsAdvertisingLanguages"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't view 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 the 120 requests a minute it shares with Get Google Ads campaign launch draft. Retry later."}}}},"/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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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/tracking-script":{"get":{"summary":"Get Google Ads tracking script","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 gtag snippet that reports the app's conversions to Google Ads.\n\nThe snippet loads Google's tag, sets consent to denied for every purpose until the app grants it, captures the click ID from the landing URL into a first-party cookie once ad storage is granted, and reports a page view on each in-app route change. It carries the conversion ID of the first goal on the account whose tag Google has published, which covers every goal on that account.\n\nPlace the snippet once in the app's HTML rather than per page. Firing an individual conversion is separate. The app calls `gtag('event', 'conversion', ...)` at the moment the conversion happens, or you report it from the server with [Upload Google Ads conversions](/api-reference/upload-google-ads-conversions).\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_tracking_script_endpoint_api_apps__app_id__google_ads_tracking_script_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 snippet to place in the app.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackingScriptResponse"}}}},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."}}}},"/api/apps/{app_id}/google-ads/tracking-scan":{"get":{"summary":"Scan Google Ads conversion tracking","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nCross-references the app's conversion mappings against the conversion calls in its code, and reports what does not line up.\n\nEach entry in `gaps` names one problem. An `error` is a mapped conversion that cannot fire, and a `warning` is a setup that works but probably is not what you meant, such as a disabled mapping or code firing a goal no mapping points at. A `missing-call-` gap also carries a `composer_prompt` you can hand to [Send chat message](/api-reference/send-chat-message) to have the AI write the fix.\n\n<Warning>Treat `error` gaps and `calls_count` as unverified. The scan reads the app's code from a copy that is empty for apps built in the current editor, so it reports `calls_count` as `0` and raises a `missing-call-` gap for every enabled mapping even when the call is present and working. Base44's own alerting re-checks these against the deployed app before acting on them, and this endpoint does not. Confirm a `missing-call-` gap against the app's real code before changing anything, and do not read `calls_count` as evidence that no conversions fire.</Warning>\n\n<Warning>`is_clean` is `true` when nothing was scanned, so read `skipped_reason` first. An app with no Google Ads account comes back with `is_clean` set to `true`, no `gaps`, and `skipped_reason` set to `no_google_ads_account`, which is not a clean bill of health. That response also omits `enabled_mappings_count`.</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":"scan_tracking_api_apps__app_id__google_ads_tracking_scan_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":"What the scan found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackingScanResult"}}}},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."}}}},"/api/apps/{app_id}/google-ads/tracking-scan/build-bootstrap-prompt":{"post":{"summary":"Build Google Ads tracking fix prompt","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 conversion goals if it does not have them, then returns an instruction that tells the AI how to install conversion tracking in the app.\n\nSend the returned `composer_prompt` to [Send chat message](/api-reference/send-chat-message) and the AI does the wiring for you. Publish the app afterwards, because Google Ads only records conversions from the published app. Pair it with [Scan Google Ads conversion tracking](/api-reference/scan-google-ads-conversion-tracking) to see what needs fixing first.\n\nThis call changes the Google Ads account. Any goal missing from the standard set is created on it, covering `PURCHASE`, `ADD_TO_CART`, `BEGIN_CHECKOUT`, `SIGNUP`, `SUBMIT_LEAD_FORM`, `CONTACT`, `REQUEST_QUOTE`, `BOOK_APPOINTMENT`, `SUBSCRIBE_PAID`, and `PHONE_CALL_LEAD`. Creating a goal is not reversible through this API. Read [List Google Ads conversion actions](/api-reference/list-google-ads-conversion-actions) first if you want to know what the account already has.\n\nSend `wire_events` as `true` to get a prompt that also wires each of the account's enabled [conversion mappings](/api-reference/list-google-ads-conversion-mappings), instead of only installing the base tag. On an account that has enabled mappings, that path waits for Google to publish the tag for each mapped goal and retries the read up to four times about a second apart, so the request can take several seconds.\n\n<Note>Only `wire_events` is supported in the body. The builder UI sends a snapshot of the app's source alongside it, which is internal and not part of this API.</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":"build_bootstrap_prompt_api_apps__app_id__google_ads_tracking_scan_build_bootstrap_prompt_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":{"content":{"application/json":{"schema":{"title":"BuildTrackingFixPrompt","type":"object","properties":{"wire_events":{"type":"boolean","description":"Whether the prompt should also wire the account's enabled conversion mappings (`true`) or only install the base tag (`false`).","default":false,"example":true}}},"example":{"wire_events":true}}}},"responses":{"200":{"description":"The prompt to send to the AI.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackingFixPromptResponse"}}}},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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 30 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 30 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 three calls a minute in the worst case rather than seven, 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"},"single_render":{"type":"boolean","description":"Skip engines that draw several candidates per shape, and charge one request per shape against the picture rate limit. Use it to replace a single picture; a shape can still return more than one picture, so use the first. When `false`, some engines draw several candidates per shape and charge for each.","default":false,"example":true}}},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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 30 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 30 requests a minute for each picture shape, so a `PERFORMANCE_MAX` call costs 4 and a `SEARCH` call costs 2. For `PERFORMANCE_MAX` the picture pool runs out first, which puts the ceiling at about seven 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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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 30 picture requests a minute. Retry later."}}}},"/api/apps/{app_id}/google-ads/asset-groups/{asset_group_id}/regenerate-assets":{"post":{"summary":"Regenerate Google Ads asset group creative","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nGenerates fresh creative for one asset group, for when its existing creative has stopped performing.\n\nUse it when [List Google Ads asset performance](/api-reference/list-google-ads-asset-performance) shows an asset group rated `POOR` or `LOW`. That endpoint tells you creative has gone stale; this is what replaces it.\n\nNothing reaches Google Ads here. The response is the same shape as [Generate Google Ads assets](/api-reference/generate-google-ads-assets), and the new creative comes back as previews you accept one at a time with [Accept a generated Google Ads asset](/api-reference/accept-a-generated-google-ads-asset). Nothing on the asset group is replaced or removed for you, so accepting adds to what is already there rather than swapping it.\n\nPass the asset group's Google Ads ID, which is numeric. Anything else is rejected with a 422 without a call to Google.\n\nThe rate limits, the two 200 shapes, and the retry accounting are the same as [Generate Google Ads assets](/api-reference/generate-google-ads-assets), because this runs the same generation underneath.\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":"regenerate_asset_group_assets_api_apps__app_id__google_ads_asset_groups__asset_group_id__regenerate_assets_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":"asset_group_id","in":"path","required":true,"schema":{"type":"string","description":"Google Ads' own numeric ID for the asset group, as reported in `asset_group_id` by [List Google Ads asset performance](/api-reference/list-google-ads-asset-performance).","title":"Asset Group Id"},"description":"Google Ads' own numeric ID for the asset group, as reported in `asset_group_id` by [List Google Ads asset performance](/api-reference/list-google-ads-asset-performance).","example":"1122334455"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"RegenerateAssetGroupAssets","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`. Asset groups belong to Performance Max campaigns, so the default is the value you almost always want.","default":"PERFORMANCE_MAX","example":"PERFORMANCE_MAX"},"final_url":{"type":"string","description":"Page the new pictures should be based on. Send this or `freeform_prompt`, not both, or omit both to ground the creative in the app's own home page.","example":"https://example.com/spring"},"freeform_prompt":{"type":"string","description":"What the new creative should show, in your own words, up to 1500 characters. Send this or `final_url`, not both.","example":"Brighter lifestyle shots with the product in use"}}},"example":{"channel_type":"PERFORMANCE_MAX","freeform_prompt":"Brighter lifestyle shots with the product in use"}}}},"responses":{"200":{"description":"The replacement 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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"409":{"description":"The app's Google Ads account is not ready to generate assets yet, or the request to Google Ads timed out after being sent. Nothing was attached to a campaign either way, so retrying is safe."},"422":{"description":"The asset group ID is not numeric, or 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 30 picture requests a minute. Retry later."},"404":{"description":"No asset group with this ID exists on the app's Google Ads account, or the campaign it belongs to is not one Base44 manages for this app."}}}},"/api/apps/{app_id}/google-ads/assets/upload-video":{"post":{"summary":"Upload a Google Ads video","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nUploads a video file to YouTube so you can use it in a campaign.\n\nGoogle Ads only takes video as a YouTube video, so a file has to reach YouTube before a campaign can show it. Base44 fetches the file from `file_url` and uploads it as an unlisted video to a YouTube channel Google manages for Base44, not to your own channel. The upload is not tied to a campaign: pass the returned `video_id` as a campaign video the same way you would pass the ID from a YouTube link. An uploaded video that no campaign uses stays on that channel, unlisted.\n\nThe request waits while the file is fetched and uploaded, and then a few seconds more while YouTube assigns the video ID. If YouTube is still processing by then, the response carries an empty `video_id` and an `upload_rn`. The upload has succeeded in that case. Do not upload the file again: call [Poll a Google Ads video upload](/api-reference/poll-a-google-ads-video-upload) with `upload_rn` every few seconds until `video_id` comes back. A file that cannot be fetched, one over 64 MB, and a video YouTube rejects return a 400.\n\nEach upload publishes a video, so this is limited to 5 uploads every 5 minutes per app. Some workspaces have a different limit. Polling has its own, looser limit. It is refused with a 409 while the workspace is on a Google Ads billing hold.\n\n<Note>This endpoint accepts a personal API key from anyone who can edit the app. A read-only key is refused with a 403, and so are workspace API keys.</Note>","operationId":"upload_video_asset_api_apps__app_id__google_ads_assets_upload_video_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":{"$ref":"#/components/schemas/GoogleAdsVideoUploadRequest"}}}},"responses":{"200":{"description":"The uploaded video, or a reference to poll while YouTube processes it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsVideoUpload"}}}},"400":{"description":"The file could not be fetched from `file_url`, including an address on a host Base44 does not fetch from and a file over 64 MB, or YouTube or Google Ads rejected the video."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't edit this app, the app does not exist, or you used a read-only or 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 workspace is on a Google Ads billing hold."},"429":{"description":"The app has used up its 5 uploads in the last 5 minutes, or 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/assets/upload-video/poll":{"post":{"summary":"Poll a Google Ads video upload","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nChecks whether YouTube has assigned an ID to a video you uploaded with [Upload a Google Ads video](/api-reference/upload-a-google-ads-video).\n\nSend the `upload_rn` that upload returned. Each call reads the upload's state from Google once and returns straight away, with `video_id` empty while YouTube is still processing. Poll every few seconds until it is filled in. Nothing is uploaded again, and polling does not count against the upload limit.\n\nA video YouTube rejected, or one that failed to upload, returns a 400, so upload a different file then. An `upload_rn` that is not shaped like one, or that belongs to a different Google Ads account, is also rejected with a 400. One that is correctly shaped but names no upload keeps returning an empty `video_id`, so stop polling after a few minutes.\n\nThis is limited to 30 requests a minute per app. Some workspaces have a different limit. It is refused with a 409 while the workspace is on a Google Ads billing hold.\n\n<Note>This endpoint accepts a personal API key from anyone who can edit the app. A read-only key is refused with a 403, and so are workspace API keys.</Note>","operationId":"poll_video_upload_api_apps__app_id__google_ads_assets_upload_video_poll_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":{"$ref":"#/components/schemas/GoogleAdsVideoUploadPollRequest"}}}},"responses":{"200":{"description":"The upload, with `video_id` filled in once YouTube has assigned it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsVideoUpload"}}}},"400":{"description":"`upload_rn` is not a valid upload reference for this app's Google Ads account, or YouTube rejected the video or failed to process it."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't edit this app, the app does not exist, or you used a read-only or 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 workspace is on a Google Ads billing hold."},"429":{"description":"The app has used up its 30 polls for the current minute, or 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/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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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. Marketing setup, campaign edits, generation and resuming also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, pausing, deletion and financial settlement remain available."},"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, the schema sets row-level security rules Base44 cannot enforce, or the schema newly declares a keyword Base44 does not implement (such as `unique`)."},"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. It also fires when the entity store rejects a schema it cannot store, such as one with an empty property name or a number too large to store. Fix the schema and send it again."},"503":{"description":"Temporary entity store error. The change may not have been saved. Send the request again."}}},"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`, a schema sets row-level security rules Base44 cannot enforce, or a schema newly declares a keyword Base44 does not implement (such as `unique`)."},"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. It also fires when the entity store rejects a schema it cannot store, such as one with an empty property name or a number too large to store. Fix the schema and send it again."},"428":{"description":"Entities the sync would delete still have records. Delete them first — the message names every one of them, so they can be cleared in one pass."},"503":{"description":"Temporary entity store error. The change may not have been saved. Send the request again."}}}},"/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`, the schema sets row-level security rules Base44 cannot enforce, or the schema newly declares a keyword Base44 does not implement (such as `unique`)."},"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. It also fires when the entity store rejects a schema it cannot store, such as one with an empty property name or a number too large to store. Fix the schema and send it again."},"503":{"description":"Temporary entity store error. The change may not have been saved. Send the request again."}}},"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."},"503":{"description":"Temporary entity store error. The change may not have been saved. Send the request again."}}}},"/api/apps/{app_id}/backend-functions":{"get":{"summary":"List backend functions","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 deployed backend functions, each with its code and the automations that run it.\n\nUse it to pull the functions' source, for example before you change one and send it back with [Redeploy a backend function](/api-reference/redeploy-a-backend-function). Every function comes back in one response, with no paging.\n\nThis is limited to 60 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused even though this endpoint only reads, and workspace API keys are not accepted.</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_functions_api_api_apps__app_id__backend_functions_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the functions belong to.","title":"App Id"},"description":"ID of the app the functions belong to.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The app's backend functions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackendFunctions"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/backend-functions/{function_name}":{"delete":{"summary":"Delete a backend function","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 of the app's backend functions, along with the automations that run it.\n\nThe function is removed from the app's main line, but the published app keeps serving it until you deploy the app again with [Deploy an app](/api-reference/deploy-an-app). Its automations are deleted straight away, so they stop for the published app too. The function's file in the app's code is left alone. The delete is rejected while main is protected.\n\nAn app runs one deploy at a time, and a delete counts as one. If another deploy for the app is still running after a short wait, the call is rejected and is safe to retry. On some apps the remaining functions are redeployed as part of the delete, and the request waits for that to finish.\n\nThis is limited to 60 requests per minute per app for each workspace's API keys, so every personal and workspace API key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app, or a workspace API key with the `apps:deploy` scope. A read-only key is refused.</Note>","operationId":"delete_function_api_api_apps__app_id__backend_functions__function_name__delete","parameters":[{"name":"function_name","in":"path","required":true,"schema":{"type":"string","description":"Name of the backend function, as `name` in [List backend functions](/api-reference/list-backend-functions). Nested functions have slashes in their name, and you send those as they are.","title":"Function Name"},"description":"Name of the backend function, as `name` in [List backend functions](/api-reference/list-backend-functions). Nested functions have slashes in their name, and you send those as they are.","example":"sendWelcomeEmail"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the function belongs to.","title":"App Id"},"description":"ID of the app the function belongs to.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"204":{"description":"The function was deleted."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, your API key is read-only, or your workspace API key lacks the `apps:deploy` scope or doesn't cover this app."},"404":{"description":"App not found, or the app has no backend function with this name."},"409":{"description":"Another deploy for the app is running, or the app's main line is protected. With a workspace API key, it also means the app left the key's workspace or the key was revoked during the request. In that case the function can already be gone, and a retry then answers that it isn't found."},"422":{"description":"`function_name` is blank, contains `.`, `$` or a control character, is `__router__` or `__prod__`, or names an actor under `base44/actors/`, which this endpoint doesn't delete."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/functions-mgmt/{function_name}/logs":{"get":{"summary":"Get backend function logs","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns recent log lines from one of the app's backend functions, newest first.\n\nA line is anything the function wrote to the console, plus the errors it didn't catch. The lines hold whatever the function logged, so they can include data about the app's users.\n\nBy default you get up to 500 lines from the last 7 days. Set `since` and `until` to pick a different window, `level` to keep one severity, and `limit` for fewer lines. Logs are kept for a limited time, so a window far in the past can come back empty.\n\nTo page back, send the `time` of the oldest line you have, plus one millisecond, as `until`, and drop the lines you already have. Several lines can share a millisecond, and this keeps you from skipping any.\n\nA function that doesn't exist, or that has never run, returns an empty list rather than an error.\n\nSome older apps whose functions haven't moved to Base44's current function runtime can window and filter their lines differently.\n\nThis is limited to 60 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key, including a read-only one, from anyone with access to the app, viewers included. Workspace API keys are not authorized for it.</Note>","operationId":"get_backend_functions_logs_api_apps__app_id__functions_mgmt__function_name__logs_get","parameters":[{"name":"function_name","in":"path","required":true,"schema":{"type":"string","description":"Name of the backend function, without a file extension. Nested functions have slashes in their name, and you send those as they are.","title":"Function Name"},"description":"Name of the backend function, without a file extension. Nested functions have slashes in their name, and you send those as they are.","example":"sendWelcomeEmail"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the function belongs to.","title":"App Id"},"description":"ID of the app the function belongs to.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"env","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Whose traffic to show. `prod` returns lines from the deployment serving the published app, and `preview` returns lines from the builder's preview. Leave it out for every line from the function's latest deployment. Any other value is ignored.","title":"Env"},"description":"Whose traffic to show. `prod` returns lines from the deployment serving the published app, and `preview` returns lines from the builder's preview. Leave it out for every line from the function's latest deployment. Any other value is ignored.","example":"prod"},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start of the window, as an ISO 8601 timestamp. A timestamp without an offset is read as UTC. Defaults to 7 days before `until`.","title":"Since"},"description":"Start of the window, as an ISO 8601 timestamp. A timestamp without an offset is read as UTC. Defaults to 7 days before `until`.","example":"2026-01-14T00:00:00Z"},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End of the window, as an ISO 8601 timestamp. A timestamp without an offset is read as UTC. Defaults to now.","title":"Until"},"description":"End of the window, as an ISO 8601 timestamp. A timestamp without an offset is read as UTC. Defaults to now.","example":"2026-01-15T09:23:41Z"},{"name":"level","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Keep only lines at this severity. Send `debug`, `info`, `warn`, or `error`, and `warning` is read as `warn`.","title":"Level"},"description":"Keep only lines at this severity. Send `debug`, `info`, `warn`, or `error`, and `warning` is read as `warn`.","example":"error"},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Maximum number of lines to return, from 1 to 500. Defaults to 500, and a larger value is treated as 500.","title":"Limit"},"description":"Maximum number of lines to return, from 1 to 500. Defaults to 500, and a larger value is treated as 500.","example":50}],"responses":{"200":{"description":"The log lines, newest first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FunctionLogLine"},"title":"Response 200 Get Backend Functions Logs Api Apps  App Id  Functions Mgmt  Function Name  Logs Get"}}}},"400":{"description":"`since` or `until` isn't a valid timestamp, `since` isn't before `until`, or `level` isn't one of the accepted values."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key."},"404":{"description":"App not found."},"422":{"description":"`limit` isn't a whole number."},"429":{"description":"Too many requests for this app in the last minute from personal API keys in your workspace."}}}},"/api/apps/{app_id}/app-checkpoints/{checkpoint_id}/file-tree":{"get":{"summary":"Get checkpoint files","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 source files saved in a [checkpoint](/developers/references/app-management/get-started/concepts#checkpoints), so you can read the app's code as it was at that version or compare two versions.\n\nThe files are the app's editable source, such as its pages, components, backend functions and entity schemas, rather than every file in its repository. They come from the commit the checkpoint saved. A checkpoint saved before the app's code was kept in git is rebuilt from the copy it stored, which holds fewer files. That copy is also used when a newer checkpoint's commit can't be read and the checkpoint stored one.\n\nEvery file comes back in one response, so expect a large response for a big app.\n\nThis is limited to 120 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused even though this endpoint only reads, and workspace API keys are not accepted.</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_checkpoint_file_tree_api_apps__app_id__app_checkpoints__checkpoint_id__file_tree_get","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 checkpoint's source files.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckpointFiles"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App not found, or the app has no checkpoint with this ID."},"429":{"description":"Rate limit exceeded."}}}},"/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. A read-only key is refused, and workspace API keys are not accepted.</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 read-only key or a workspace API key. These endpoints take a full-access 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. A read-only key is refused, and workspace API keys are not accepted.</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 read-only key or a workspace API key. These endpoints take a full-access 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. A read-only key is refused, and workspace API keys are not accepted.</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/UserApp"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a read-only key or a workspace API key. These endpoints take a full-access personal API key."},"404":{"description":"App not found, or the app has no checkpoint with this ID."},"409":{"description":"The app is working on a message, 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}/app-checkpoints/{checkpoint_id}/retry-build":{"post":{"summary":"Retry checkpoint build","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 new preview build for a [checkpoint](/developers/references/app-management/get-started/concepts#checkpoints) and returns without waiting for it to finish.\n\nUse it when a checkpoint's `preview_status` in [List checkpoints](/api-reference/list-checkpoints) is `failed`. It works on a checkpoint in any state. When the checkpoint's commit already has a build, nothing is rebuilt and the checkpoint is marked `ready` straight away.\n\nThe build runs in the background. Poll [List checkpoints](/api-reference/list-checkpoints) and watch the checkpoint's `preview_status` until it's `ready` or `failed`. Build failures can include details in `build_error.message`.\n\nOnly one build of a commit runs at a time. Retrying while another build of the same commit is still running doesn't start a second one in parallel. The retry waits for that build and only builds again if it didn't succeed. If the wait expires without a completed build, the checkpoint stays `building` while it's within the build execution budget. Once more than 20 minutes have passed since the latest checkpoint update or build start, listing checkpoints checks for a completed build and marks the checkpoint `ready` or `failed`. If build availability can't be checked, its status stays unchanged. You can retry a failed checkpoint's build.\n\n<Warning>A checkpoint whose `git_commit_hash` is `null` was saved before the app's code was kept in git. Retrying its build first saves that checkpoint's code to the app as a new commit, which changes the app's current code.</Warning>\n\n<Note>This endpoint accepts a personal API key. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"retry_checkpoint_build_api_apps__app_id__app_checkpoints__checkpoint_id__retry_build_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":"Where the build stands, and the commit being built.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RetryCheckpointBuildResponse"}}}},"400":{"description":"The checkpoint has no commit to build."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a read-only key or a workspace API key. These endpoints take a full-access 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, so its state can't be changed directly."}}}},"/api/apps/{app_id}/branches/main/protection":{"put":{"summary":"Set main branch protection","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nTurns protection of the app's main branch on or off.\n\nWhile main is protected, calls that change main directly are refused with a 409, and the builder's AI can't edit main either. Make changes on a branch and merge it back instead. Setting the value the app already has changes nothing. Read the current value from `main_branch_protected` in [Get app](/api-reference/get-app).\n\nOnly the app owner or an admin of the app's workspace can change it. Editors can't, even though they can edit the app. This endpoint is limited to 10 requests per minute.\n\n<Note>This endpoint accepts a personal API key belonging to the app owner or a workspace admin. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"update_main_branch_protection_api_apps__app_id__branches_main_protection_put","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose main branch to protect or unprotect.","title":"App Id"},"description":"ID of the app whose main branch to protect or unprotect.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MainBranchProtection"}}}},"responses":{"200":{"description":"The main branch's protection after the change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MainBranchProtection"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you aren't the app owner or a workspace admin, or your API key is read-only."},"404":{"description":"App not found."},"422":{"description":"`protected` is missing or isn't a boolean, or the body has other fields."},"429":{"description":"Rate limit exceeded. The base limit is 10 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/branches":{"post":{"summary":"Create 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 [branch](/developers/references/app-management/get-started/concepts#branches) of the app from its main branch. The branch has its own copy of the app's code, so its changes don't reach main until you [merge the branch](/api-reference/merge-branch).\n\nSet `branch_name` to name the branch, or send `prompt` and Base44 generates a short name from it. `prompt` only names the branch. It isn't sent to the AI. When a generated name is taken, Base44 adds a number to it. A `branch_name` you choose must not belong to another active or merged branch.\n\nThe branch starts from main's latest saved state. If the AI is working on main at the time, the branch starts from main as it was before that change. Set `from_message_id` to start from an earlier point instead. Send at least one of `branch_name`, `prompt`, and `from_message_id`.\n\nIn this response, `created_by_name` is always `null`. [Get branch](/api-reference/get-branch) returns it.\n\nThis endpoint is limited to 30 requests per minute, shared with Create branch, Delete branch and Merge branch.\n\n<Note>You can't send chat messages to a branch through the API yet, so a branch gets changes of its own only from work in the Base44 editor. Until it has some, [Merge branch](/api-reference/merge-branch) refuses it with a 409.</Note>\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</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_branch_api_apps__app_id__branches_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to create the branch in.","title":"App Id"},"description":"ID of the app to create the branch in.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBranchRequest"}}}},"responses":{"200":{"description":"The new branch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BranchSummary"}}}},"400":{"description":"`branch_name` is `main`, or you set `from_message_id` on an app imported from GitHub, or the message's saved version belongs to a branch rather than main."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found, or `from_message_id` isn't in main's conversation or has no saved version to start from."},"409":{"description":"An active or merged branch is already named `branch_name`."},"422":{"description":"None of `branch_name`, `prompt`, and `from_message_id` is set, `branch_name` breaks the naming rules or is over 100 characters, or `from_message_id` is over 64 characters."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}},"get":{"summary":"List branches","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 [branches](/developers/references/app-management/get-started/concepts#branches), newest first. Branches imported with [Import a GitHub branch](/api-reference/import-a-github-branch) are included. While [main branch protection](/api-reference/set-main-branch-protection) is on, changes to main go through a branch.\n\nBy default only active branches are returned. Set `include_merged` to `true` to also get the ones already merged into main. Deleted branches are never listed, but [Get branch](/api-reference/get-branch) still returns them. The list isn't paginated.\n\nThis is limited to 300 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. List branches and Get branch share it. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app, including a read-only key. Workspace API keys are not accepted.</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_branches_api_apps__app_id__branches_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose branches to read.","title":"App Id"},"description":"ID of the app whose branches to read.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"include_merged","in":"query","required":false,"schema":{"type":"boolean","description":"Set to `true` to also return branches already merged into main.","default":false,"title":"Include Merged"},"description":"Set to `true` to also return branches already merged into main.","example":true}],"responses":{"200":{"description":"The app's branches, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Branches"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or you used a workspace API key."},"404":{"description":"App not found."},"422":{"description":"`include_merged` isn't a boolean."},"429":{"description":"Rate limit exceeded. The base limit is 300 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/branches/{branch_id}":{"get":{"summary":"Get branch","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 of the app's branches, whatever its status, including merged and deleted branches.\n\nThis is limited to 300 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. List branches and Get branch share it. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app, including a read-only key. Workspace API keys are not accepted.</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_branch_api_apps__app_id__branches__branch_id__get","parameters":[{"name":"branch_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the branch, as returned by [List branches](/api-reference/list-branches).","title":"Branch Id"},"description":"ID of the branch, as returned by [List branches](/api-reference/list-branches).","example":"68f1a2b3c4d5e6f708192a3b"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose branches to read.","title":"App Id"},"description":"ID of the app whose branches to read.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The branch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BranchSummary"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or you used a workspace API key."},"404":{"description":"App not found, or the app has no branch with this ID."},"429":{"description":"Rate limit exceeded. The base limit is 300 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}},"delete":{"summary":"Delete branch","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nDeletes an active branch and discards its changes. Its code, its preview, and the entity and backend function changes made on it are removed, along with comments left on its preview. A deleted branch can't be restored.\n\nAfter deletion, [List branches](/api-reference/list-branches) no longer returns the branch, but [Get branch](/api-reference/get-branch) still does, with `status` set to `deleted`. Its name is free to use again. Merged and already deleted branches can't be deleted.\n\nThis endpoint is limited to 30 requests per minute, shared with Create branch, Delete branch and Merge branch.\n\n<Tip>A 409 response carries an `extra_data.reason` code that says why the request was refused.</Tip>\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"delete_branch_api_apps__app_id__branches__branch_id__delete","parameters":[{"name":"branch_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the branch, as returned by [List branches](/api-reference/list-branches).","title":"Branch Id"},"description":"ID of the branch, as returned by [List branches](/api-reference/list-branches).","example":"68f1a2b3c4d5e6f708192a3b"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the branch belongs to.","title":"App Id"},"description":"ID of the app the branch belongs to.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The branch was deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedBranch"}}}},"400":{"description":"The branch isn't active: it was already merged or deleted."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found, or the app has no branch with this ID."},"409":{"description":"The AI is working on the branch, the branch is being updated from main, or its latest changes are still being saved."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/branches/{branch_id}/sync-status":{"get":{"summary":"Get branch sync status","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 far an active branch is behind main, whether updating it from main is expected to conflict, and whether an update is running. Check it before you call [Update branch from main](/api-reference/update-branch-from-main), and poll it while an update runs.\n\nAn update that has been running for more than 15 minutes with nothing working on the branch counts as stuck. This endpoint rolls a stuck update back and returns the branch to `idle`, so it can be updated again.\n\nThis endpoint is limited to 120 requests per minute.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"get_branch_sync_status_api_apps__app_id__branches__branch_id__sync_status_get","parameters":[{"name":"branch_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the branch, as returned by [List branches](/api-reference/list-branches).","title":"Branch Id"},"description":"ID of the branch, as returned by [List branches](/api-reference/list-branches).","example":"68f1a2b3c4d5e6f708192a3b"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the branch belongs to.","title":"App Id"},"description":"ID of the app the branch belongs to.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"How the branch stands against main.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BranchSyncStatus"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found, or the app has no active branch with this ID."},"429":{"description":"Rate limit exceeded. The base limit is 120 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/branches/{branch_id}/sync-from-main":{"post":{"summary":"Update branch from main","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nBrings main's latest changes into an active branch. When main's code changed, Base44 merges it into the branch. Messages sent on main since the branch was created or last updated are added to the branch's conversation as context for the AI. When nothing changed on main, nothing happens and `updated` is `false`.\n\nIf main's changes conflict with the branch's, the AI resolves the conflicts on the branch before the request returns. That's an AI turn: it uses credits like a chat message, and the request can stay open for several minutes. If the AI needs your input to finish, `sync_status.sync_state` is `needs_input`. The branch then stays locked until you answer in the Base44 editor, and it can't be updated, merged, or deleted until then. If the AI can't resolve the conflicts, the request fails and the branch is restored to how it was before the update.\n\nIn this response, `branch.created_by_name` is always `null`. [Get branch](/api-reference/get-branch) returns it.\n\nThis endpoint is limited to 10 requests per minute.\n\n<Tip>A 409 response carries an `extra_data.reason` code that says why the request was refused.</Tip>\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</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":"sync_branch_from_main_api_apps__app_id__branches__branch_id__sync_from_main_post","parameters":[{"name":"branch_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the branch, as returned by [List branches](/api-reference/list-branches).","title":"Branch Id"},"description":"ID of the branch, as returned by [List branches](/api-reference/list-branches).","example":"68f1a2b3c4d5e6f708192a3b"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the branch belongs to.","title":"App Id"},"description":"ID of the app the branch belongs to.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The branch after the update.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BranchSyncResult"}}}},"400":{"description":"The update needs the AI to resolve conflicts, and you've run out of credits."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found, or the app has no active branch with this ID."},"409":{"description":"The branch can't be updated right now. `extra_data.reason` is `app_processing` or `branch_processing` while the AI is working on main or on the branch, `branch_sync_in_progress` while another update runs or waits for input, and `branch_sync_branch_moved` when the branch changed during the update. Any other reason means recent changes are still being saved. Try again shortly."},"429":{"description":"Rate limit exceeded. The base limit is 10 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/branches/{branch_id}/merge":{"post":{"summary":"Merge branch","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nMerges an active branch into the app's main branch. Base44 merges the branch's code into main, applies its entity and backend function changes to main, saves a checkpoint, and adds a message to main's conversation that summarizes the merge. The branch's `status` then becomes `merged`, and it can't be changed after that. This works while [main branch protection](/api-reference/set-main-branch-protection) is on.\n\nThe request returns once the merge is done. A 409 leaves the branch active and main unchanged. Its `extra_data.reason` tells you what to do:\n\n- `branch_has_no_changes`: the branch has no code changes to merge.\n- `branch_merge_conflict`: the branch conflicts with main. [Update the branch from main](/api-reference/update-branch-from-main) to resolve the conflicts, then merge again.\n- `app_processing` or `branch_processing`: the AI is working on main or on the branch. Try again once it's done.\n- `branch_sync_in_progress`: the branch is being updated from main. Try again once [Get branch sync status](/api-reference/get-branch-sync-status) shows `sync_state` as `idle`.\n- `branch_pending_commit_wait_failed`: the branch's latest changes are still being saved. Try again shortly.\n\nIn this response, `branch.created_by_name` is always `null`. [Get branch](/api-reference/get-branch) returns it.\n\nThis endpoint is limited to 30 requests per minute, shared with Create branch, Delete branch and Merge branch.\n\n<Note>You can't send chat messages to a branch through the API yet, so a branch gets changes of its own only from work in the Base44 editor. Until it has some, merging it returns a 409 with `branch_has_no_changes`.</Note>\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</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":"merge_branch_api_apps__app_id__branches__branch_id__merge_post","parameters":[{"name":"branch_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the branch, as returned by [List branches](/api-reference/list-branches).","title":"Branch Id"},"description":"ID of the branch, as returned by [List branches](/api-reference/list-branches).","example":"68f1a2b3c4d5e6f708192a3b"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the branch belongs to.","title":"App Id"},"description":"ID of the app the branch belongs to.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The merged branch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MergedBranch"}}}},"400":{"description":"The branch isn't active: it was already merged or deleted."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found, or the app has no branch with this ID."},"409":{"description":"The branch has no changes to merge, conflicts with main, or is busy. `extra_data.reason` says which."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/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."}}},"delete":{"summary":"Delete secret","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 secret from the app.\n\nDeleting a secret redeploys the app's backend functions so they stop receiving it. A function that still reads the name gets no value for it, so remove or update that code too.\n\nDeleting is permanent. To use the secret again, set it anew with [Set secret](/api-reference/set-secret).\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"delete_secret_api_api_apps__app_id__secrets_delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to delete the secret from.","title":"App Id"},"description":"ID of the app to delete the secret from.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"secret_name","in":"query","required":true,"schema":{"type":"string","description":"Name of the secret to delete, as returned by [List secrets](/api-reference/list-secrets).","title":"Secret Name"},"description":"Name of the secret to delete, as returned by [List secrets](/api-reference/list-secrets).","example":"STRIPE_SECRET_KEY"}],"responses":{"200":{"description":"The secret was deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSecretResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or your API key is read-only."},"404":{"description":"App not found, or the app has no secret with this name."},"422":{"description":"`secret_name` is missing."}}},"get":{"summary":"List secrets","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 secrets stored on the app.\n\nValues are never returned. Every name maps to the same masked placeholder, `••••••••`, so use this to check which secrets are set, not what they hold. The list covers every secret stored on the app, including ones Base44 generated or an integration stored, not only the ones you set with [Set secret](/api-reference/set-secret). Remove one with [Delete secret](/api-reference/delete-secret).\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"get_app_secrets_api_api_apps__app_id__secrets_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose secrets you want to list.","title":"App Id"},"description":"ID of the app whose secrets you want to list.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The names of the app's secrets.","content":{"application/json":{"schema":{"type":"object","title":"SecretNames","description":"Each key is a secret name, and each value is the masked placeholder `••••••••`.","additionalProperties":{"type":"string"}},"example":{"STRIPE_SECRET_KEY":"••••••••","SENDGRID_API_KEY":"••••••••"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or your API key is read-only."},"404":{"description":"App not found."}}}},"/api/apps/{app_id}/metadata/update-logo":{"post":{"summary":"Set app logo","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 app's logo to the image at `logo_url`.\n\nThe URL is stored as you send it. It isn't fetched or checked, so use a link that stays reachable, such as the public link from [Upload app file](/api-reference/upload-app-file). The builder shows the new logo right away, and the published app shows it after you next [deploy the app](/api-reference/deploy-an-app). Link previews use the logo when the app has no social image.\n\nGenerating a logo is limited to 10 requests an hour per app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</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_logo_api_apps__app_id__metadata_update_logo_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to set the logo on.","title":"App Id"},"description":"ID of the app to set the logo on.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","title":"SetAppLogo","required":["logo_url"],"properties":{"logo_url":{"type":"string","description":"URL of the image to use as the app's logo.","example":"https://storage.base44.com/6820f3a4e7b91d003c45a1f2/3f2504e0_logo.png"}}},"example":{"logo_url":"https://storage.base44.com/6820f3a4e7b91d003c45a1f2/3f2504e0_logo.png"}}}},"responses":{"200":{"description":"The app with its new logo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppLogo"}}}},"400":{"description":"`logo_url` is missing."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or your API key is read-only."},"404":{"description":"App not found."},"429":{"description":"Rate limit reached. Retry later."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/metadata/social-image":{"post":{"summary":"Set social image","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 image shown when someone shares a link to the app, or removes it.\n\nSend an `https` URL, such as the public link from [Upload app file](/api-reference/upload-app-file), or `null` to remove the image. Leaving `social_image_url` out also removes it. The URL isn't fetched or checked. The published app picks up the change within moments, without a new deploy. A preview image set for a specific page takes precedence on that page, and without a social image, link previews fall back to the app's logo.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</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_social_image_api_apps__app_id__metadata_social_image_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to set the social image on.","title":"App Id"},"description":"ID of the app to set the social image on.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SocialImagePayload"}}}},"responses":{"200":{"description":"The app with its new social image.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSocialImage"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or your API key is read-only."},"404":{"description":"App not found."},"422":{"description":"`social_image_url` isn't an `https` URL, or it's longer than 2048 characters."}}}},"/api/apps/{app_id}/metadata/generate-logo":{"post":{"summary":"Generate app logo","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nGenerates a logo image for the app from a text description and returns its URL.\n\nDescribe the logo you want in `prompt`. An AI model turns it into a prompt for a flat app icon, and an image model draws it. The call returns once the image is ready, which usually takes several seconds.\n\nGenerating doesn't change the app's logo. The image is saved with the app's files. To use it, send its `logo_url` to [Set app logo](/api-reference/set-app-logo).\n\nGenerating a logo is limited to 10 requests an hour per app. Some workspaces have a different limit. Every logo generated for the app counts toward the same limit, including ones generated in the builder, and a request counts even when generation fails.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"generate_logo_url_api_apps__app_id__metadata_generate_logo_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to generate a logo for.","title":"App Id"},"description":"ID of the app to generate a logo for.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","title":"GenerateAppLogo","required":["prompt"],"properties":{"prompt":{"type":"string","description":"Description of the logo you want, in your own words.","example":"A wooden chair on a warm orange background"}}},"example":{"prompt":"A wooden chair on a warm orange background"}}}},"responses":{"200":{"description":"The generated logo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoUrlResponse"}}}},"400":{"description":"`prompt` is missing, or the image model declined to draw it. Try a different description."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or your API key is read-only."},"404":{"description":"App not found."},"429":{"description":"Rate limit reached. Retry later."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/metadata/slug":{"patch":{"summary":"Change app slug","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 app's slug, which sets its `<slug>.base44.app` address.\n\nSend `null` to reset the slug to the one Base44 generates from the app's name and ID. Sending the current slug, in any capitalization, changes nothing.\n\n<Warning>The old address stops working as soon as the slug changes, and nothing redirects it to the new one. Change the slug before you share the app's URL or point ads at it.</Warning>\n\nAn app listed on Launchpad can't change its slug while a voting round is live, because the slug is also the listing's public URL.\n\nChanging the slug is limited to 30 requests an hour per caller. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it.</Note>","operationId":"update_slug_api_apps__app_id__metadata_slug_patch","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/UpdateSlugPayload"}}}},"responses":{"200":{"description":"The app, with its new slug.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserApp"}}}},"400":{"description":"The slug is malformed, reserved, or already used by another app.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SlugRejected"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have edit access to this app, or you used a workspace API key. This endpoint takes a personal API key."},"404":{"description":"App not found."},"409":{"description":"The app is listed on Launchpad and a voting round is live."},"429":{"description":"Rate limit reached. Retry later."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/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. Track queued messages with [Get message queue](/api-reference/get-message-queue). On an app that queues messages, every message you send while the queue is paused is queued until you call [Resume message queue](/api-reference/resume-message-queue).\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\nIf messages are waiting in the [queue](/api-reference/get-message-queue), stopping also pauses it, so none of them start and new messages you send join it. Call [Resume message queue](/api-reference/resume-message-queue) to carry on.\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\nWhen `branch_id` is supplied, the message and checkpoint must belong to that active branch. The rollback changes only that branch and creates a new commit on its Git ref; Main and other branches are unchanged.\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 or branch is busy, the branch is read-only, or the checkpoint belongs to a different 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\nQueued messages wait while this runs, and the [queue](/api-reference/get-message-queue) carries on afterward unless you paused it. If the new turn ends waiting on a tool call, the queue stays paused until you answer it with [Submit tool-call input](/api-reference/submit-tool-call-input).\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}/chat/queue":{"get":{"summary":"Get message queue","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 messages waiting to run in the app's AI chat, next to run first, and whether the queue is paused.\n\nWhen the AI is busy, [Send chat message](/api-reference/send-chat-message) can queue your message instead of running it and return `{\"queued\": true}`. Queued messages run one at a time, in order, each as its own turn, once the turn before it finishes. The queue holds up to 7 messages, and a queue nobody changes for 24 hours is emptied. Base44 queues messages on apps that more than one person edits, so on other apps the queue is usually empty.\n\nThe API acts on the queue of the app's main line. Queues on branches aren't available through it.\n\n[Stop generation](/api-reference/stop-generation) pauses a queue that has messages in it, so nothing queued starts after you stop. [Edit and resend](/api-reference/edit-and-resend) pauses the queue while it runs and resumes it afterward, unless the AI ends that turn waiting on a tool call. A turn that ends waiting on a tool call also pauses the queue until you answer it with [Submit tool-call input](/api-reference/submit-tool-call-input). Base44 doesn't lift a pause you set yourself, but every pause expires 24 hours after it was set.\n\nThis is limited to 120 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app, including a read-only key. Workspace API keys are not accepted.</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_queue_api_api_apps__app_id__chat_queue_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose message queue this request acts on.","title":"App Id"},"description":"ID of the app whose message queue this request acts on.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The app's message queue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageQueue"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or you used a workspace API key."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded. The base limit is 120 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}},"delete":{"summary":"Clear message queue","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRemoves every queued message and lifts any pause, so the next message you send runs as soon as the AI is free. The turn that's already running isn't affected. Stop it with [Stop generation](/api-reference/stop-generation).\n\nA message that was just taken off the queue to run is cancelled too, if its turn hasn't started yet.\n\nA collaborator in the editor can send a queued message into the turn that's running. While that's happening the message can't be changed or removed. Clearing the queue is refused until it finishes.\n\nThis is limited to 60 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Every endpoint that changes the queue shares it. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</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":"clear_queue_api_api_apps__app_id__chat_queue_delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose message queue this request acts on.","title":"App Id"},"description":"ID of the app whose message queue this request acts on.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The emptied, unpaused queue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageQueue"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, you're a viewer in the app's workspace, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"409":{"description":"A collaborator is sending a queued message into the turn that's running. Try again once it finishes."},"429":{"description":"Rate limit exceeded. The base limit is 60 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/chat/queue/reorder":{"put":{"summary":"Reorder message queue","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 order queued messages run in. Pass the IDs in the new order.\n\nA message that joined the queue after you read it, and so isn't in your list, keeps its place after the ones you list.\n\nThis is limited to 60 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Every endpoint that changes the queue shares it. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</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":"reorder_queue_api_api_apps__app_id__chat_queue_reorder_put","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose message queue this request acts on.","title":"App Id"},"description":"ID of the app whose message queue this request acts on.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReorderQueuePayload"}}}},"responses":{"200":{"description":"The queue in its new order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageQueue"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, you're a viewer in the app's workspace, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"422":{"description":"The request body is missing `item_ids`, it isn't a list of strings, or it repeats an ID."},"429":{"description":"Rate limit exceeded. The base limit is 60 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/chat/queue/{item_id}":{"delete":{"summary":"Remove queued message","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRemoves one message from the queue so it never runs. Removing a message that's no longer queued isn't an error.\n\nA collaborator in the editor can send a queued message into the turn that's running. While that's happening the message can't be changed or removed.\n\nThis is limited to 60 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Every endpoint that changes the queue shares it. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</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":"remove_queue_item_api_api_apps__app_id__chat_queue__item_id__delete","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the queued message to remove. Get it from `items[].id` in [Get message queue](/api-reference/get-message-queue).","title":"Item Id"},"description":"ID of the queued message to remove. Get it from `items[].id` in [Get message queue](/api-reference/get-message-queue).","example":"3b9d2f6e-8c41-4a7e-9f05-1d2c3b4a5e6f"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose message queue this request acts on.","title":"App Id"},"description":"ID of the app whose message queue this request acts on.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The queue without the message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageQueue"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, you're a viewer in the app's workspace, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"409":{"description":"A collaborator is sending this message into the turn that's running. Try again once it finishes."},"429":{"description":"Rate limit exceeded. The base limit is 60 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}},"put":{"summary":"Edit queued message","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 text of a queued message before it runs. Its place in the queue doesn't change.\n\nOnce a message starts running it leaves the queue, so it can no longer be edited.\n\nA collaborator in the editor can send a queued message into the turn that's running. While that's happening the message can't be changed or removed.\n\nThis is limited to 60 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Every endpoint that changes the queue shares it. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</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":"edit_queue_item_api_api_apps__app_id__chat_queue__item_id__put","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the queued message to edit. Get it from `items[].id` in [Get message queue](/api-reference/get-message-queue).","title":"Item Id"},"description":"ID of the queued message to edit. Get it from `items[].id` in [Get message queue](/api-reference/get-message-queue).","example":"3b9d2f6e-8c41-4a7e-9f05-1d2c3b4a5e6f"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose message queue this request acts on.","title":"App Id"},"description":"ID of the app whose message queue this request acts on.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditQueueItemPayload"}}}},"responses":{"200":{"description":"The queue with the edited message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageQueue"}}}},"400":{"description":"`content` is longer than 100,000 characters."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, you're a viewer in the app's workspace, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found, or no message with this ID is queued. It may already have started."},"409":{"description":"A collaborator is sending this message into the turn that's running. Try again once it finishes."},"422":{"description":"The request body is missing `content`, or it isn't a string."},"429":{"description":"Rate limit exceeded. The base limit is 60 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/chat/queue/pause":{"post":{"summary":"Pause message queue","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nPauses the queue. Queued messages wait until you call [Resume message queue](/api-reference/resume-message-queue), and the turn that's already running carries on.\n\nOn an app that queues messages, the ones you send with [Send chat message](/api-reference/send-chat-message) while the queue is paused join it instead of running. On other apps a send runs right away even while the queue is paused, so pausing doesn't stop new turns there.\n\nBase44 doesn't lift a pause you set, but it expires 24 hours after you set it.\n\nThis is limited to 60 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Every endpoint that changes the queue shares it. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</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_queue_api_api_apps__app_id__chat_queue_pause_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose message queue this request acts on.","title":"App Id"},"description":"ID of the app whose message queue this request acts on.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The paused queue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageQueue"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, you're a viewer in the app's workspace, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded. The base limit is 60 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/chat/queue/resume":{"post":{"summary":"Resume message queue","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nLifts the queue's pause, whether you set it or Base44 did, and starts the next queued message once the AI is free. Resuming a queue that isn't paused also starts the next message if the AI is idle.\n\n[Stop generation](/api-reference/stop-generation) pauses a queue that has messages in it, so nothing queued starts after you stop. [Edit and resend](/api-reference/edit-and-resend) pauses the queue while it runs and resumes it afterward, unless the AI ends that turn waiting on a tool call. A turn that ends waiting on a tool call also pauses the queue until you answer it with [Submit tool-call input](/api-reference/submit-tool-call-input). Base44 doesn't lift a pause you set yourself, but every pause expires 24 hours after it was set. Call this to carry on.\n\nThis is limited to 60 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Every endpoint that changes the queue shares it. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</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":"resume_queue_api_api_apps__app_id__chat_queue_resume_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose message queue this request acts on.","title":"App Id"},"description":"ID of the app whose message queue this request acts on.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The queue, no longer paused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageQueue"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, you're a viewer in the app's workspace, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded. The base limit is 60 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/coding/write":{"post":{"summary":"Save an app file","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 file into the app, the same way saving in the app editor does.\n\nBase44 writes the file, applies it to the app, and takes care of what the path implies: a backend function is redeployed, an entity or agent schema is parsed and validated, and a change that affects row-level security records a revertible notice on the app's chat.\n\nSending an empty `content` deletes the file. That is the app editor's own convention for this path, so treat an empty string as a delete rather than as a way to blank a file.\n\nPaths are relative to the app root. Entity and agent schemas, workflows and email templates each have one canonical location, and a legacy shorthand is refused with a 400 naming the canonical form. A path that escapes the app is refused the same way.\n\nWriting a backend function deploys it, so the request takes as long as the deploy and answers 422 carrying the compiler's diagnostics when the code doesn't build. Backend functions also need a Builder plan or higher on the app's workspace. Use [Save several app files](/api-reference/save-several-app-files) to write a batch of frontend files in one call.\n\nThe response is the app document as it stands after the change, the same shape [Get app](/api-reference/get-app) returns.\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":"write_file_api_api_apps__app_id__coding_write_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose code to change.","title":"App Id"},"description":"ID of the app whose code to change.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"SaveAppFile","type":"object","required":["file_path","content"],"properties":{"file_path":{"type":"string","description":"Path of the file, relative to the app root."},"content":{"type":"string","description":"Full contents to save. An empty string deletes the file, which is this endpoint's convention rather than a way to blank one."}}},"example":{"file_path":"src/pages/About.jsx","content":"export default function About() {\n  return <h1>About</h1>;\n}\n"}}}},"responses":{"200":{"description":"The app after the write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserApp"}}}},"400":{"description":"`file_path` escapes the app, or uses a legacy location for an entity or agent schema, a workflow or an email template instead of its canonical one."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key. Writing a backend function also needs a Builder plan or higher on the app's workspace."},"404":{"description":"App not found."},"409":{"description":"The app is on a branch this change can't be made on: a branch that has been merged or closed, a protected main, or a path the branch refuses."},"422":{"description":"A backend function in this write failed to build. The detail carries the compiler diagnostics."}}}},"/api/apps/{app_id}/coding/write-batch":{"post":{"summary":"Save several app files","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSaves several files into the app in one call, the way the app editor does.\n\nSend up to 50 files. Every path is validated before anything is written, so one bad path fails the whole batch with a 400 and leaves the app untouched. An empty `files` list is a 400 rather than a no-op.\n\nThis route never deploys, so it refuses backend code: a path under `functions/` answers 422 rather than writing source that the live function wouldn't match. Use [Save an app file](/api-reference/save-an-app-file) for those, one at a time, and this one for the frontend files a visual edit touches.\n\nInternally this route can follow a feature branch, but the parameter that selects one isn't part of the public API, so writes through this endpoint land on the app's main line.\n\nThe response is the app document as it stands after the change, the same shape [Get app](/api-reference/get-app) returns.\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":"write_batch_api_api_apps__app_id__coding_write_batch_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose code to change.","title":"App Id"},"description":"ID of the app whose code to change.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"SaveAppFiles","type":"object","required":["files"],"properties":{"files":{"type":"array","minItems":1,"maxItems":50,"description":"The files to save, 1 to 50 of them. All are validated before any is written.","items":{"type":"object","title":"SaveAppFilesEntry","required":["file_path","content"],"properties":{"file_path":{"type":"string","description":"Path of the file, relative to the app root."},"content":{"type":"string","description":"Full contents to save. An empty string deletes the file, which is this endpoint's convention rather than a way to blank one."}}}}}},"example":{"files":[{"file_path":"src/pages/About.jsx","content":"export default function About() {\n  return <h1>About</h1>;\n}\n"},{"file_path":"src/pages/Home.jsx","content":"export default function Home() {\n  return <h1>Home</h1>;\n}\n"}]}}}},"responses":{"200":{"description":"The app after the writes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserApp"}}}},"400":{"description":"`files` is empty, holds more than 50 entries, has a path that escapes the app or uses a legacy schema, workflow or email location, or a configuration file with invalid JSON."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key."},"404":{"description":"App not found."},"409":{"description":"The app is on a branch this change can't be made on: a branch that has been merged or closed, a protected main, or a path the branch refuses."},"422":{"description":"One of the paths is backend code, which this route can't write because it never redeploys."}}}},"/api/apps/{app_id}/coding/redeploy-function/{function_name}":{"post":{"summary":"Redeploy a backend function","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nDeploys the code you send as one of the app's backend functions and saves it as that function's source, the same way **Save & Redeploy** does in the app editor.\n\nThe function is deployed from the `code` in the request, not from the source already stored in the app. To redeploy a function unchanged, send its current source. The deploy runs even when the code is identical, which is how you recover a function whose deployment is missing or out of date. A name that no function has yet creates a new function.\n\nThe request waits for the deploy to finish, and fails with the compiler's diagnostics when the code doesn't build. An app runs one deploy at a time. If another deploy for the app is still running after a short wait, the call is rejected and is safe to retry.\n\nBackend functions need a Builder plan or higher on the app's workspace. If the app has no backend functions yet, the first redeploy sets them up.\n\nThe deploy always lands on the app's main line, and is rejected while main is protected.\n\nThis is limited to 50 requests a minute per caller for each app. Some workspaces have a different limit.\n\nThe response is the app document as it stands after the change, the same shape [Get app](/api-reference/get-app) returns.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</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":"redeploy_function_api_apps__app_id__coding_redeploy_function__function_name__post","parameters":[{"name":"function_name","in":"path","required":true,"schema":{"type":"string","description":"Name of the backend function, as a path under `functions/`. Send the bare name, the name with its file extension, or the name followed by `/entry.ts`. Nested functions have slashes in their name, and you send those as they are.","title":"Function Name"},"description":"Name of the backend function, as a path under `functions/`. Send the bare name, the name with its file extension, or the name followed by `/entry.ts`. Nested functions have slashes in their name, and you send those as they are.","example":"sendWelcomeEmail"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose code to change.","title":"App Id"},"description":"ID of the app whose code to change.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"RedeployBackendFunction","type":"object","required":["code"],"properties":{"code":{"type":"string","description":"Full source of the function to deploy and save.","example":"Deno.serve(async (req) => {\n  return Response.json({ ok: true });\n});\n"}}},"example":{"code":"Deno.serve(async (req) => {\n  return Response.json({ ok: true });\n});\n"}}}},"responses":{"200":{"description":"The app after the deploy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserApp"}}}},"400":{"description":"`function_name` is empty, isn't a valid function name, or points at one of the app's frontend files instead of a backend function."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, your API key is read-only, or you used a workspace API key. Backend functions also need a Builder plan or higher on the app's workspace."},"404":{"description":"App not found."},"409":{"description":"The app's main line is protected, the app moved to another workspace during the deploy, or another deploy for the app is still running."},"422":{"description":"The body has no `code`, or the code was rejected or failed to build. A build failure carries the compiler diagnostics in the detail."},"429":{"description":"Too many redeploys for this app from you in the last minute."},"503":{"description":"`deployment_temporarily_unavailable`: the app's backend functions are being upgraded to a new runtime and the upgrade didn't finish. Nothing was deployed. Retry after a few minutes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}}}}},"/api/apps/{app_id}/remix":{"post":{"summary":"Remix 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\nCreates a copy of an app in your workspace and makes you its owner. The app must be in the workspace your personal access token is scoped to, and the remix is created there too, so you need an editor-capable role in it. You can remix an app you own, collaborate on as an editor, or administer as a workspace admin, an approved template, or an app its owner marked as remixable and didn't make private. Remixing a paid template requires that you bought it first. Call [Get remix requirements](/api-reference/get-remix-requirements) first to see which secrets the app needs.\n\nThe remix is named after the app with ` (Copy)` appended. When the app is an approved template, the remix copies the approved version of the template, name included, rather than the app's current state, unless you set `with_chat_history`.\n\nThe body takes only the fields below. Fields that [Create app](/api-reference/create-app) accepts, such as `name`, `user_description`, `public_settings`, `custom_instructions`, and `prevent_iframe_embedding`, are ignored here. The remix copies `user_description`, `public_settings`, and `custom_instructions` from the app and starts with `prevent_iframe_embedding` set to `true`. Rename the remix or change its description afterwards with [Update app](/api-reference/update-app). The API has no endpoint for changing the other settings yet.\n\nThe response returns as soon as the new app exists. Setting up its files can continue in the background, so poll [Get app](/api-reference/get-app) until its `status.state` is no longer `processing`. A token limited to selected apps doesn't gain access to the new app, so use a token with access to all apps in the workspace if you need to reach the remix afterwards. This endpoint is limited to 10 requests per minute.\n\n<Warning>The new app is created before `secret_values` are applied and before the code is copied. A request that fails at either step, for example on an invalid `secret_values` entry, still leaves the new app in your workspace, and it counts toward your plan's app limit. Delete it before you retry.</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":"remix_app_api_api_apps__app_id__remix_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to remix.","title":"App Id"},"description":"ID of the app to remix.","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":{"type":"object","title":"RemixApp","properties":{"with_chat_history":{"type":"boolean","default":false,"description":"Whether to copy the app's chat history too. The remix then copies the app's current state, even when the app is an approved template. Only the app's owner, its editor-level collaborators, and its workspace admins can set it.","example":false},"secret_values":{"type":"object","description":"Secrets to set on the remix, keyed by secret name. Set `type` to `value` to provide the secret's value. Set `type` to `clone` to copy the value of a secret on the source app, named by `value` or, when you omit `value`, by the key itself. You can clone a secret only from an app you own. Except for `user_agent` apps, you can't clone one when the remix copies an approved template version.","additionalProperties":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["value","clone"],"description":"`value` to set the secret to `value`, or `clone` to copy a secret from the source app.","example":"value"},"value":{"type":"string","description":"The secret's value when `type` is `value`. When `type` is `clone`, the name of the source app's secret to copy, which defaults to the key.","example":"sk_test_example"}}},"example":{"STRIPE_API_KEY":{"type":"value","value":"sk_test_example"},"SENDGRID_API_KEY":{"type":"clone"}}}}},"example":{"secret_values":{"STRIPE_API_KEY":{"type":"value","value":"sk_test_example"}}}}}},"responses":{"200":{"description":"The new app.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserApp"}}}},"400":{"description":"You can't remix the app, its type can't be remixed, you set `with_chat_history` without owning the app, collaborating on it as an editor, or administering its workspace, or a `secret_values` entry is malformed or clones a secret while the remix copies an approved template version of an app that isn't a `user_agent` app."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"Your token is read-only, the app is blocked (`app_blocked`), the app is a paid template you haven't bought, your role, plan, or account doesn't let you create another app of this type in the workspace, Superagent is disabled in the workspace for a `user_agent` app, or a `secret_values` entry clones a secret from an app you don't own."},"404":{"description":"The app doesn't exist, is outside the workspace or apps your token is scoped to, or a secret that a `secret_values` entry clones doesn't exist on it."},"409":{"description":"The app's connected GitHub repository can't be reached. Its owner has to reconnect GitHub."},"429":{"description":"Rate limit exceeded (10 requests per minute)."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/remix/requirements":{"get":{"summary":"Get remix requirements","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 you need to know before you [remix an app](/api-reference/remix-an-app): its name and type, the secrets it uses, the ones the remix copies for you, and whether you can remix it with its chat history. The app must be in the workspace your personal access token is scoped to.\n\nA successful response means you pass the remix permission checks on the app. The remix itself can still be refused, when the app's `app_type` can't be remixed, when your role or plan doesn't let you create another app in the workspace, or when the app is a `user_agent` app and Superagent is disabled in the workspace. Read-only tokens are refused here too, even though this endpoint doesn't change anything.","operationId":"remix_app_requirements_api_apps__app_id__remix_requirements_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to remix.","title":"App Id"},"description":"ID of the app to remix.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"What remixing the app involves.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemixRequirements"}}}},"400":{"description":"You can't remix the app."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"Your token is read-only, the app is blocked (`app_blocked`), or the app is a paid template you haven't bought."},"404":{"description":"The app doesn't exist, or it is outside the workspace or apps your token is scoped to."}}}},"/api/apps/{app_id}/delete-check":{"get":{"summary":"Check app delete impact","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nShows what deleting the app affects, so you can check before you call [Delete app](/api-reference/delete-app).\n\nThe response lists the template listings made from the app, the custom domains connected to it, how many users it has, and any Google Ads campaigns that are still serving. Delete app refuses while a campaign is serving, so pause those first. It works for an app that's already in the trash too.\n\nYou need editor access to the app. Passing this check doesn't mean you can delete the app: only its owner or a workspace admin can.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</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":"check_app_delete_dependencies_api_apps__app_id__delete_check_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to check.","title":"App Id"},"description":"ID of the app to check.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"What deleting the app affects.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppDeleteImpact"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or your API key is read-only."},"404":{"description":"App not found."}}}},"/api/apps/{app_id}/assets":{"get":{"summary":"List 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 files in the app's media library, newest first unless you set `sort`. That includes files uploaded to the app and files Base44 generated for it, such as AI-generated images and videos.\n\nYou can filter by:\n- Text in the file name\n- How the file got into the library\n- File type\n\nYou get up to 500 assets, and there's no pagination. Use the filters to narrow a larger library.\n\nThis is limited to 120 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key, including a read-only one, from anyone with access to the app, viewers included. Workspace API keys are not authorized for 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.</Warning>","operationId":"list_assets_api_apps__app_id__assets_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose media library to read.","title":"App Id"},"description":"ID of the app whose media library to read.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Text to match, ignoring case, anywhere in the file name. For example, `logo` matches `Logo-dark.png`.","title":"Search"},"description":"Text to match, ignoring case, anywhere in the file name. For example, `logo` matches `Logo-dark.png`.","example":"logo"},{"name":"sort","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Order of the list. Either `newest`, `oldest`, or `a-z` for file name order, where uppercase letters come before lowercase ones. Defaults to `newest`, and any other value also sorts newest first.","default":"newest","title":"Sort"},"description":"Order of the list. Either `newest`, `oldest`, or `a-z` for file name order, where uppercase letters come before lowercase ones. Defaults to `newest`, and any other value also sorts newest first.","example":"a-z"},{"name":"source_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Keep only files with this `source_type`, either `upload` or `generated`.","title":"Source Type"},"description":"Keep only files with this `source_type`, either `upload` or `generated`.","example":"upload"},{"name":"file_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Keep only files with this `file_type`, matched exactly. For example, `png`.","title":"File Type"},"description":"Keep only files with this `file_type`, matched exactly. For example, `png`.","example":"png"}],"responses":{"200":{"description":"The app's assets.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Assets"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key."},"404":{"description":"App not found."},"429":{"description":"Too many requests for this app in the last minute from personal API keys in your workspace."}}},"post":{"summary":"Create asset","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 file that's already online to the app's media library, by its URL. Base44 doesn't download the file. To store a file with Base44, use [Upload asset](/api-reference/upload-asset) instead.\n\nIn this response, `created_date` and `updated_date` include a `+00:00` offset. [List assets](/api-reference/list-assets) returns them without one.\n\nThis endpoint is limited to 120 requests per minute, shared with Create asset, Rename asset and Delete asset. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</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_asset_api_apps__app_id__assets_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose media library to change.","title":"App Id"},"description":"ID of the app whose media library to change.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"file_name":{"description":"Name of the file. Characters other than letters, digits, `_`, `.` and `-` become `_`, anything up to the last `/` is dropped, and an empty name becomes `untitled`.","example":"Logo-dark.png","title":"File Name","type":"string"},"file_url":{"description":"Public `http` or `https` URL of the file. Base44 doesn't download or copy it, so the asset stays a link to this URL. Addresses on private networks and Base44's own hosts, other than its media CDN, are refused.","example":"https://media.base44.com/files/public/6820f3a4e7b91d003c45a1f2/Logo-dark.png","title":"File Url","type":"string"},"source_type":{"default":"upload","description":"How the file got into the library, `upload` or `generated`. Defaults to `upload`.","enum":["upload","generated"],"example":"upload","title":"Source Type","type":"string"},"file_type":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"File extension without the dot, such as `png`. Stored as you send it, and used by the `file_type` filter in List assets. Leave it out to store `null`.","example":"png","title":"File Type"}},"required":["file_name","file_url"],"title":"CreateAsset","type":"object"},"example":{"file_name":"Logo-dark.png","file_url":"https://cdn.acme.com/brand/Logo-dark.png","file_type":"png"}}}},"responses":{"200":{"description":"The new asset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetSummary"}}}},"400":{"description":"`file_url` isn't an `http` or `https` URL, or points to an address that isn't allowed."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"422":{"description":"`file_name` or `file_url` is missing, or `source_type` isn't `upload` or `generated`."},"429":{"description":"Rate limit exceeded. The base limit is 120 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/assets/{asset_id}":{"patch":{"summary":"Rename asset","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nChanges an asset's file name in the media library. Only the name changes: `file_url` and `file_type` stay the same, so code that uses the URL keeps working.\n\nThis endpoint is limited to 120 requests per minute, shared with Create asset, Rename asset and Delete asset. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</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":"rename_asset_api_apps__app_id__assets__asset_id__patch","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the asset to rename. Get it from `id` in [List assets](/api-reference/list-assets).","title":"Asset Id"},"description":"ID of the asset to rename. Get it from `id` in [List assets](/api-reference/list-assets).","example":"68b0e1f2a3c4d5e6f7a8b9c0"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose media library to change.","title":"App Id"},"description":"ID of the app whose media library to change.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"file_name":{"description":"New name of the file. Characters other than letters, digits, `_`, `.` and `-` become `_`, anything up to the last `/` is dropped, and an empty name becomes `untitled`.","example":"Logo-light.png","title":"File Name","type":"string"}},"required":["file_name"],"title":"RenameAsset","type":"object"},"example":{"file_name":"Logo-light.png"}}}},"responses":{"200":{"description":"The renamed asset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetSummary"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found, or the app has no asset with this ID."},"422":{"description":"`file_name` is missing."},"429":{"description":"Rate limit exceeded. The base limit is 120 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}},"delete":{"summary":"Delete asset","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 asset from the app's media library. This can't be undone.\n\nWhen the file is stored with Base44 for this app, the file is deleted too, and its URL stops working, including in the app's code. A file added by URL from somewhere else stays where it is. The asset is removed even if deleting the file fails.\n\nThis endpoint is limited to 120 requests per minute, shared with Create asset, Rename asset and Delete asset. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"delete_asset_api_apps__app_id__assets__asset_id__delete","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the asset to delete. Get it from `id` in [List assets](/api-reference/list-assets).","title":"Asset Id"},"description":"ID of the asset to delete. Get it from `id` in [List assets](/api-reference/list-assets).","example":"68b0e1f2a3c4d5e6f7a8b9c0"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose media library to change.","title":"App Id"},"description":"ID of the app whose media library to change.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The asset was removed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedAsset"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found, or the app has no asset with this ID."},"429":{"description":"Rate limit exceeded. The base limit is 120 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/assets/upload":{"post":{"summary":"Upload asset","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nUploads a file to the app's media library. Send it as `multipart/form-data` in a field named `file`.\n\nThe file name decides what's accepted. It must end in one of these extensions: `3g2`, `3gp`, `avi`, `csv`, `docx`, `fbx`, `gif`, `glb`, `gltf`, `ico`, `jpeg`, `jpg`, `json`, `m4v`, `md`, `mkv`, `mov`, `mp3`, `mp4`, `mtl`, `obj`, `ogv`, `pdf`, `png`, `svg`, `txt`, `wav`, `webm`, `webp`, `wmv`, `xlsx`, `zip`. HTML files aren't accepted. The size limit depends on the type, from 1 MB for `ico` to 100 MB for audio and video. Most images can be up to 40 MB. The asset's `file_name` is the uploaded file's name with the same character rules as [Create asset](/api-reference/create-asset), and its `file_type` is the extension, in lowercase.\n\nThe file is stored at a public URL, returned in `file_url`, so anyone with the URL can open it.\n\nIn this response, `created_date` and `updated_date` include a `+00:00` offset. [List assets](/api-reference/list-assets) returns them without one.\n\nThis endpoint is limited to 60 requests per minute. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</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":"upload_to_assets_api_apps__app_id__assets_upload_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose media library to change.","title":"App Id"},"description":"ID of the app whose media library to change.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/UploadAsset"}}}},"responses":{"200":{"description":"The new asset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetSummary"}}}},"400":{"description":"The file name has no supported extension, or the file is over the size limit for its type."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"422":{"description":"`file` is missing."},"429":{"description":"Rate limit exceeded. The base limit is 60 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/users/invite-users":{"post":{"summary":"Invite 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\nInvites people to the app by email. Each address gets an invitation email with a link to the app.\n\nSet `role` to the role they get in the app, `user` unless you say otherwise. Set `collaborator_role` to `editor` to also let them edit the app in Base44. An editor invitation only goes to members of the app's workspace unless you also set `add_as_guest`, which adds anyone else to the workspace as a guest. Only workspace editors and admins can add guests.\n\nAn invitation that fails doesn't fail the call. You get a 200 with one entry per address in `results`, so check `success` on each. Invited people appear in [List app users](/api-reference/list-app-users) once they sign in to the app.\n\nEvery address counts toward the app's daily invitation allowance, which depends on your plan, including addresses whose invitation then fails. A call that would go over it is refused with a 429 and sends nothing.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"invite_users_api_apps__app_id__users_invite_users_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to invite the users to.","title":"App Id"},"description":"ID of the app to invite the users to.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","title":"InviteAppUsers","required":["user_emails"],"properties":{"user_emails":{"type":"array","items":{"type":"string"},"maxItems":50,"description":"Email addresses to invite, up to 50 per call.","example":["jane@acme.com","sam@acme.com"]},"role":{"type":"string","default":"user","description":"Role the invited people get in the app, such as `user` or `admin`. It isn't checked against the roles the app defines.","example":"user"},"collaborator_role":{"type":"string","enum":["editor"],"description":"Set to `editor` to also let them edit the app in Base44.","example":"editor"},"add_as_guest":{"type":"boolean","default":false,"description":"With `collaborator_role` set to `editor`, adds anyone outside the app's workspace to it as a guest instead of failing their invitation.","example":false}}},"example":{"user_emails":["jane@acme.com","sam@acme.com"],"role":"user"}}}},"responses":{"200":{"description":"The outcome of each invitation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteUsersResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, your API key is read-only, or you're a workspace viewer inviting an admin or an editor."},"404":{"description":"App not found."},"422":{"description":"`user_emails` is missing or has more than 50 addresses, or `collaborator_role` isn't `editor`."},"429":{"description":"Too many invitations. The base limit is 6 requests per minute. A personal API key shares it with every invitation call made with keys in the same workspace, across all its apps. The app's daily invitation allowance also applies. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/users/provisions":{"post":{"summary":"Provision an app user","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nGives a person access to this app ahead of their first sign-in, so the app can be embedded signed in as them. Returns `created` for a new email, and `exists` for an email that already has an access request, whatever its state: a request still pending approval stays pending, and an embed sign-in token for it answers `unknown_user`. A workspace API key needs the **Provision app users** permission. Limited to 120 requests a minute per app, shared with deprovisioning. Higher plans get a higher limit.","operationId":"provision_user_api_apps__app_id__users_provisions_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/ProvisionUserPayload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvisionUserResponse"}}}},"400":{"description":"`role` is not one of the app's roles: error.code invalid_role."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The key can't provision users of this app."},"404":{"description":"App not found."},"429":{"description":"The app went over its per-minute limit for provision and deprovision requests combined."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"PersonalAccessTokenAuth":[]},{"WorkspaceApiKeyAuth":[]}]},"delete":{"summary":"Deprovision an app user","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 person's access to this app, including their app user if they've already signed in. A workspace API key needs the **Provision app users** permission. Limited to 120 requests a minute per app, shared with provisioning. Higher plans get a higher limit.","operationId":"deprovision_user_api_apps__app_id__users_provisions_delete","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/DeprovisionUserPayload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeprovisionUserResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The email belongs to the app's owner or to someone with platform-level access: error.code privileged_user."},"404":{"description":"The email isn't provisioned for this app: error.code unknown_user."},"429":{"description":"The app went over its per-minute limit for provision and deprovision requests combined."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"PersonalAccessTokenAuth":[]},{"WorkspaceApiKeyAuth":[]}]}},"/api/apps/{app_id}/users/collaborators":{"get":{"summary":"List collaborators","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 people who can edit the app in Base44, owner first, then everyone else by email. Invite more with [Invite app users](/api-reference/invite-app-users) and `collaborator_role` set to `editor`. The list isn't paginated.\n\nGuests in the app's workspace can't list collaborators. Base44 staff who can edit the app are left out, unless they own it or are a guest in its workspace.\n\nThis is limited to 60 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. List collaborators and Remove collaborator share it. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app, including a read-only key. Workspace API keys are not accepted.</Note>","operationId":"list_collaborators_api_apps__app_id__users_collaborators_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose collaborators to list.","title":"App Id"},"description":"ID of the app whose collaborators to list.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The app's collaborators, owner first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Collaborators"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you're a guest in the app's workspace, the app is blocked, or you used a workspace API key."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded. The base limit is 60 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/users/collaborators/{collaborator_id}":{"delete":{"summary":"Remove collaborator","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nTakes away a collaborator's access to edit the app in Base44. Only the app owner can do this, and the owner can't be removed.\n\nBy default the person stays a user of the app with the role they had, so they can still sign in to it. Set `also_remove_from_app` to `true` to remove them from the app's users as well. If they connected the app to Google Business Profile or Plaid, those connections are disconnected.\n\nA collaborator whose `collaborator_source` is `workspace` joined because the app is open to everyone in its workspace, so they can join again the next time they open it while it stays open.\n\nThis is limited to 60 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. List collaborators and Remove collaborator share it. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to the app owner. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"remove_collaborator_api_apps__app_id__users_collaborators__collaborator_id__delete","parameters":[{"name":"collaborator_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the collaborator, as returned by [List collaborators](/api-reference/list-collaborators).","title":"Collaborator Id"},"description":"ID of the collaborator, as returned by [List collaborators](/api-reference/list-collaborators).","example":"68b2d4f6a8c0e2f4a6b8c0d2"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to remove the collaborator from.","title":"App Id"},"description":"ID of the app to remove the collaborator from.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"also_remove_from_app","in":"query","required":false,"schema":{"type":"boolean","description":"Set to `true` to also remove the person from the app's users, not just its collaborators.","default":false,"title":"Also Remove From App"},"description":"Set to `true` to also remove the person from the app's users, not just its collaborators.","example":false}],"responses":{"200":{"description":"The collaborator was removed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveCollaboratorResult"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't the app owner, `collaborator_id` is the owner's, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found, or the app has no user with this `collaborator_id`."},"409":{"description":"The collaborator is creating a Google Business Profile for the app. Try again once that finishes."},"422":{"description":"`also_remove_from_app` isn't a boolean."},"429":{"description":"Rate limit exceeded. The base limit is 60 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/ownership/transfer/preflight":{"post":{"summary":"Check ownership transfer","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nChecks whether an ownership transfer invitation can be sent to an email address, and lists what would transfer with the app. Nothing is sent or changed.\n\nRead `can_send` for the answer. A problem such as an app that already has a pending transfer comes back in `blockers` rather than as an error, and the same problem stops [Start ownership transfer](/api-reference/start-ownership-transfer). A recipient without a Base44 account yet is only a warning, since they can sign up before they accept.\n\nUse `transfer_package` to choose the `integration_decisions` and `secret_decisions` you send with the invitation.\n\nYou need to own the app or be an owner or admin of its workspace. A workspace admin who doesn't own the app also needs an Enterprise workspace or approved partner status. An invitation to someone outside the workspace needs that status too, and only workspace owners and admins can send one. The workspace's transfer policy can narrow that to owners, or turn it off. If you can't manage ownership transfers for this app, the result has a `NOT_APP_OWNER` blocker. For an invitation outside the workspace, a missing Enterprise or partner status is rejected instead.\n\nThis is limited to 60 requests a minute per workspace. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"preflight_transfer_api_apps__app_id__ownership_transfer_preflight_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/PreflightTransferRequest"}}}},"responses":{"200":{"description":"The result of the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OwnershipTransferPreflightResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"Your API key is read-only, you used a workspace API key, or the app's workspace isn't Enterprise and you aren't an approved partner, for an invitation outside the workspace."},"404":{"description":"The app doesn't exist, or it's outside the workspace or apps your API key is scoped to."},"422":{"description":"`new_owner_email` is missing, or `destination` isn't `same_workspace` or `external_email`."},"429":{"description":"Rate limit reached. Retry later."}}}},"/api/apps/{app_id}/ownership/transfer":{"post":{"summary":"Start ownership transfer","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nInvites a new owner for the app by email, which starts an ownership transfer.\n\nThe recipient gets an email with a link, and the transfer completes only once they sign in and accept it. Nothing about the app changes until then. The invitation expires after 7 days. Check first that it can be sent, and what would transfer with the app, with [Check ownership transfer](/api-reference/check-ownership-transfer). Afterwards, follow it with [Get pending ownership transfer](/api-reference/get-pending-ownership-transfer), [Resend ownership transfer](/api-reference/resend-ownership-transfer), or [Cancel ownership transfer](/api-reference/cancel-ownership-transfer).\n\nWhen the recipient accepts, they pick the workspace that receives the app. If it's a different workspace, the app's current collaborators lose access and its AI chat history is cleared. Integrations and secrets follow the choices in the request. To pass the app to a member of its workspace right away, with no invitation, use [Transfer ownership to a workspace member](/api-reference/transfer-ownership-to-a-workspace-member).\n\nAn app can have only one pending transfer. Starting another while one is pending is rejected, whoever it's for. An expired invitation is replaced.\n\nYou need to own the app or be an owner or admin of its workspace. A workspace admin who doesn't own the app also needs an Enterprise workspace or approved partner status. An invitation to someone outside the workspace needs that status too, and only workspace owners and admins can send one. The workspace's transfer policy can narrow that to owners, or turn it off.\n\nThis is limited to 30 requests a minute per workspace. Some workspaces have a different limit. Invitation emails are also limited to 5 an hour and 20 a day per caller, and to 3 an hour and 10 a day per recipient. Resending counts toward the same limits.\n\n<Note>This endpoint accepts a personal API key. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"initiate_transfer_api_apps__app_id__ownership_transfer_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/InitiateTransferRequest"}}}},"responses":{"200":{"description":"The new transfer, waiting for the recipient to accept it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PendingOwnershipTransfer"}}}},"400":{"description":"The new owner is you or the app's current owner, their account is disabled, the app already has a pending transfer or is blocked, a `same_workspace` recipient isn't an owner, admin, editor, or member of the workspace, or `integration_decisions` or `secret_decisions` is empty while the app has integrations or secrets, names one the app doesn't have, or transfers a secret that has no value."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't manage ownership transfers for this app, the workspace's transfer policy doesn't let you invite someone outside it, the app came from the marketplace, your API key is read-only, you used a workspace API key, or the app's workspace isn't Enterprise and you aren't an approved partner, for an invitation outside the workspace."},"404":{"description":"The app doesn't exist, or it's outside the workspace or apps your API key is scoped to."},"409":{"description":"The app is moving to another workspace."},"422":{"description":"`new_owner_email` is missing or isn't a valid email address, or another field has an invalid value."},"429":{"description":"Rate limit or invitation email limit reached. Retry later."}}}},"/api/apps/{app_id}/ownership/transfer/workspace-member":{"post":{"summary":"Transfer ownership to a workspace member","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nMakes an owner, admin, editor, or member of the app's workspace the app's owner right away, with no invitation.\n\nUnlike [Start ownership transfer](/api-reference/start-ownership-transfer), the recipient doesn't have to accept. The app stays in its workspace, and only its owner changes. The new owner gets editor access, and the app's collaborators and published site stay as they are. The previous owner loses their direct access unless you set `keep_sender_as_collaborator`, though a workspace owner or admin keeps access through the workspace.\n\nAn app with a secret, or with a connected integration of its own, can't be transferred this way. Invite the new owner instead. An app with a pending ownership transfer, or one that's moving to another workspace, is rejected too.\n\nYou need to own the app, or be an owner or admin of its workspace with an Enterprise workspace or approved partner status.\n\nThis is limited to 30 requests a minute per workspace. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"transfer_to_workspace_member_api_apps__app_id__ownership_transfer_workspace_member_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/TransferToWorkspaceMemberRequest"}}}},"responses":{"200":{"description":"The app, with its new owner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptTransferResponse"}}}},"400":{"description":"The new owner doesn't exist, is you or the app's current owner, or has a disabled account, or the app is blocked, has a pending ownership transfer, is moving to another workspace, or has a secret or a connected integration of its own."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't manage ownership transfers for this app, the new owner isn't an owner, admin, editor, or member of the app's workspace, the app came from the marketplace, your API key is read-only, or you used a workspace API key."},"404":{"description":"The app doesn't exist, or it's outside the workspace or apps your API key is scoped to."},"409":{"description":"The app's owner or workspace changed while the transfer was being applied."},"422":{"description":"`new_owner_user_id` is missing or empty."},"429":{"description":"Rate limit reached. Retry later."}}}},"/api/apps/{app_id}/ownership/transfer/resend":{"post":{"summary":"Resend ownership transfer","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSends the app's pending ownership transfer invitation to its recipient again.\n\nA transfer starts when the app's owner or a workspace admin invites a new owner by email, with [Start ownership transfer](/api-reference/start-ownership-transfer) or in the builder. The recipient gets an email with a link, and the transfer completes only once they sign in and accept it. Nothing about the app changes until then. An invitation expires 7 days after it's sent. Resending restarts those 7 days, including for an invitation that already expired. The link in earlier emails keeps working. Check which transfer is pending with [Get pending ownership transfer](/api-reference/get-pending-ownership-transfer).\n\nA transfer can't be resent once the recipient has started accepting it, or once the app's owner or workspace has changed since it was sent.\n\nYou need to own the app or be an owner or admin of its workspace. A workspace admin who doesn't own the app also needs an Enterprise workspace or approved partner status. For a transfer to someone outside the workspace, the owner has to be a workspace owner or admin too, with the same Enterprise or partner requirement.\n\nInvitation emails are limited to 5 an hour and 20 a day per caller, and to 3 an hour and 10 a day per recipient. Starting a transfer counts toward the same limits.\n\n<Note>This endpoint accepts a personal API key. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"resend_transfer_api_apps__app_id__ownership_transfer_resend_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":"The transfer, with its new expiry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PendingOwnershipTransfer"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't manage ownership transfers for this app, your API key is read-only, or you used a workspace API key."},"404":{"description":"The app doesn't exist, it's outside the workspace or apps your API key is scoped to, it has no pending transfer, or the recipient has started accepting it."},"409":{"description":"The app's owner or workspace has changed since the transfer was sent, or the app has been blocked."},"429":{"description":"Invitation email limit reached. Retry later."}}}},"/api/apps/{app_id}/ownership/transfer/cancel":{"post":{"summary":"Cancel ownership transfer","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nCancels the app's pending ownership transfer, so the recipient can no longer accept it.\n\nThe link in the invitation email stops working right away, and the app stays with its current owner. Check which transfer is pending with [Get pending ownership transfer](/api-reference/get-pending-ownership-transfer). An expired invitation can still be cancelled. Once the recipient has started accepting, the transfer can't be cancelled for 15 minutes, while their acceptance is being applied.\n\nYou need to own the app or be an owner or admin of its workspace. A workspace admin who doesn't own the app also needs an Enterprise workspace or approved partner status. For a transfer to someone outside the workspace, the owner has to be a workspace owner or admin too, with the same Enterprise or partner requirement.\n\nThis is limited to 30 requests a minute per workspace. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"cancel_transfer_api_apps__app_id__ownership_transfer_cancel_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":"The cancelled transfer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelledOwnershipTransfer"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't manage ownership transfers for this app, your API key is read-only, or you used a workspace API key."},"404":{"description":"The app doesn't exist, it's outside the workspace or apps your API key is scoped to, it has no pending transfer, or the recipient started accepting it less than 15 minutes ago."},"409":{"description":"The recipient started accepting the transfer while it was being cancelled."},"429":{"description":"Rate limit reached. Retry later."}}}},"/api/apps/{app_id}/ownership/transfer/pending":{"get":{"summary":"Get pending ownership transfer","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 pending ownership transfer, or `null` in `transfer` when it has none.\n\nA transfer starts when the app's owner or a workspace admin invites a new owner by email, with [Start ownership transfer](/api-reference/start-ownership-transfer) or in the builder. The recipient gets an email with a link, and the transfer completes only once they sign in and accept it. Nothing about the app changes until then. An invitation expires 7 days after it's sent. An expired invitation isn't returned. Send it again with [Resend ownership transfer](/api-reference/resend-ownership-transfer), or withdraw it with [Cancel ownership transfer](/api-reference/cancel-ownership-transfer).\n\nYou need to own the app or be an owner or admin of its workspace. A workspace admin who doesn't own the app also needs an Enterprise workspace or approved partner status. For a transfer to someone outside the workspace, the owner has to be a workspace owner or admin too, with the same Enterprise or partner requirement.\n\nThis is limited to 120 requests a minute per workspace. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"get_pending_transfer_api_apps__app_id__ownership_transfer_pending_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":"The app's pending transfer, if any.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PendingOwnershipTransferResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't manage ownership transfers for this app, your API key is read-only, or you used a workspace API key."},"404":{"description":"The app doesn't exist, or it's outside the workspace or apps your API key is scoped to."},"429":{"description":"Rate limit reached. Retry later."}}}},"/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\nFindings ignored with [Ignore security finding](/api-reference/ignore-security-finding) are still listed. Leave out any whose `fingerprint` is in `ignored_fingerprints` to see only the open ones.\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.","title":"App Id"},"description":"ID of the app.","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.","title":"App Id"},"description":"ID of the app.","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}/security/scan/core-integrations/protect":{"post":{"summary":"Enable core integration protection","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 core integration protection for the app. Once it's on, the app's pages can no longer call Base44's built-in integrations, such as sending email or calling a language model, directly. Those calls have to come from the app's backend functions instead. File uploads and signed file URLs keep working as before.\n\nOn an app that only invited users or workspace members can open, those signed-in users can still make the calls directly. The protection matters most for an app anyone can open, where anyone could otherwise call the app's integrations.\n\nCheck `core_integration_recommendation` in [Get security scan](/api-reference/get-security-scan) first. `compatible` means turning protection on is safe. The call is refused while the app's code, or its published version, still calls a restricted integration directly. It's also refused until the app is published, so [deploy the app](/api-reference/deploy-an-app) first when the recommendation is `publish_required`.\n\nThe change applies right away. This endpoint can't turn the protection off.\n\nThis is limited to 30 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"enable_core_integration_protection_api_apps__app_id__security_scan_core_integrations_protect_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":"Protection is on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CoreIntegrationProtectionEnabled"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, your API key is read-only, or you used a workspace API key. Also returned when protection is already on or the app can't use backend functions, with the message `Core Integration protection is not available`."},"404":{"description":"App not found."},"409":{"description":"Turning protection on now would break the app, the app isn't published, or a change to who can open it isn't published yet. The message is `Core Integration calls must be migrated or published before protection is enabled`. It's `App changed while Core Integration protection was being enabled` when the app's code changed during the call."},"429":{"description":"Too many requests for this app in the last minute."}}}},"/api/apps/{app_id}/security/scan/ignore":{"post":{"summary":"Ignore security finding","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nIgnores one finding in the app's security scan, so it stays hidden across rescans for as long as it doesn't change. Get the finding's `fingerprint` from [Get security scan](/api-reference/get-security-scan), and send the list it came from as `finding_type`.\n\nIgnoring doesn't change the app, and it doesn't remove the finding from the scan result. The finding stays in its list, and its fingerprint is added to `ignored_fingerprints`. The Base44 editor hides findings whose fingerprint is there.\n\nA finding gets a new `fingerprint` when what it flags changes, for example when the file it points at is edited. It then shows up again, and the next scan drops the old ignore.\n\nA successful call doesn't confirm that anything was recorded. Nothing changes when the finding is already ignored, when the app has never been scanned, or when `finding_type` isn't one of the five lists. Base44 doesn't check `fingerprint` against the scan result either, so read `ignored_fingerprints` to confirm. Undo it with [Restore security finding](/api-reference/restore-security-finding).\n\nThis is limited to 60 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"ignore_finding_api_apps__app_id__security_scan_ignore_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/IgnoreFindingPayload"}}}},"responses":{"200":{"description":"The call was accepted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecurityFindingIgnoreStatus"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"422":{"description":"The body is missing or isn't a JSON object, has fields other than `fingerprint`, `finding_type`, `file_path`, and `title`, or leaves out `fingerprint` or `finding_type`. Also returned when a value isn't a string, is longer than its limit, or is empty for `fingerprint` or `finding_type`."},"429":{"description":"Too many ignores for this app in the last minute."}}}},"/api/apps/{app_id}/security/scan/ignore/{fingerprint}":{"delete":{"summary":"Restore security finding","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nUndoes [Ignore security finding](/api-reference/ignore-security-finding) for one finding, so it's no longer hidden. Its fingerprint leaves `ignored_fingerprints` in [Get security scan](/api-reference/get-security-scan). The finding itself stayed in the scan result the whole time, so nothing else changes.\n\nThe call succeeds without changing anything when the fingerprint isn't ignored. That includes an ignore a later scan already dropped because the finding changed.\n\nThis is limited to 60 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"restore_finding_api_apps__app_id__security_scan_ignore__fingerprint__delete","parameters":[{"name":"fingerprint","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":128,"description":"Fingerprint of the ignored finding, as listed in `ignored_fingerprints` by [Get security scan](/api-reference/get-security-scan). Up to 128 characters.","title":"Fingerprint"},"description":"Fingerprint of the ignored finding, as listed in `ignored_fingerprints` by [Get security scan](/api-reference/get-security-scan). Up to 128 characters.","example":"3f9a1c0e8b7d4a6f2e5c9b1d0a8f7e6c5b4a3d2e1f0c9b8a7d6e5f4c3b2a1d0e"},{"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 call was accepted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecurityFindingIgnoreStatus"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"422":{"description":"`fingerprint` is longer than 128 characters."},"429":{"description":"Too many restores for this app in the last minute."}}}},"/api/apps/{app_id}/security/scan/rls-recommendation/{entity_name}":{"delete":{"summary":"Dismiss RLS recommendation","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRemoves one entity's row-level security recommendation from the app's latest security scan result. Get `entity_name` from `rls_recommendations` in [Get security scan](/api-reference/get-security-scan).\n\nThe call changes only the scan result, never the entity's rules. There are two ways to use it:\n- To record a fix, apply the rules with [Update entity schema](/api-reference/update-entity-schema) first, then send the same rules as `applied_rule`. Base44 records them as you send them and doesn't check them against the entity.\n- To dismiss the recommendation without changing anything, leave `applied_rule` out.\n\nEither way the recommendation moves into the scan's resolved history, along with who resolved it, when, and the recommendation as it stood.\n\nThe next scan that checks row-level security judges every entity again and clears that history, so a recommendation can come back if the entity's rules still need to change.\n\nThe call is rejected when the latest result no longer holds a recommendation for the entity. That happens when the app has no scan result, when the result came from an earlier version of the scanner, when the recommendation was already resolved, or when a newer scan replaced it. Read the result again with Get security scan before you retry.\n\nThis is limited to 30 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"dismiss_rls_recommendation_api_apps__app_id__security_scan_rls_recommendation__entity_name__delete","parameters":[{"name":"entity_name","in":"path","required":true,"schema":{"type":"string","description":"Entity whose recommendation to remove, as returned in `rls_recommendations` by [Get security scan](/api-reference/get-security-scan). It contains only letters, digits, and underscores.","title":"Entity Name"},"description":"Entity whose recommendation to remove, as returned in `rls_recommendations` by [Get security scan](/api-reference/get-security-scan). It contains only letters, digits, and underscores.","example":"Order"},{"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":{"title":"DismissRlsRecommendation","type":"object","properties":{"applied_rule":{"anyOf":[{"type":"object","properties":{"create":{"anyOf":[{"type":"boolean"},{"type":"object","additionalProperties":true},{"type":"null"}],"description":"Rule you applied for creating a record. `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.","example":true},"read":{"anyOf":[{"type":"boolean"},{"type":"object","additionalProperties":true},{"type":"null"}],"description":"Rule you applied for reading records. `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.","example":{"created_by":"{{user.email}}"}},"update":{"anyOf":[{"type":"boolean"},{"type":"object","additionalProperties":true},{"type":"null"}],"description":"Rule you applied for updating a record. `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.","example":{"created_by":"{{user.email}}"}},"delete":{"anyOf":[{"type":"boolean"},{"type":"object","additionalProperties":true},{"type":"null"}],"description":"Rule you applied for deleting a record. `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.","example":{"$or":[{"created_by":"{{user.email}}"},{"user_condition":{"role":"admin"}}]}},"write":{"anyOf":[{"type":"boolean"},{"type":"object","additionalProperties":true},{"type":"null"}],"description":"Legacy rule for updating and deleting, used for whichever of the two has no rule of its own. `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.","example":{"created_by":"{{user.email}}"}}}},{"type":"null"}],"description":"The rules you applied to the entity, keyed by operation in the same format as the entity's `rls`. Send it to record the call as a fix. Leave it out, or send `null` or an empty object, to record a plain dismissal. Keys other than the five operations are rejected.\n\nA filter can use only the query operators `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$and`, `$or`, `$not`, `$nor`, `$exists`, `$type`, `$all`, `$elemMatch`, `$size`, `$regex`, `$options`, `$text`, `$search`, `$near`, `$nearSphere`, `$geoIntersects`, and `$geoWithin`. `$and`, `$or`, and `$nor` take a list. `$in`, `$nin`, and `$all` take a list or a template string such as `{{user.data.departments}}`. `user_condition` takes an object.\n\nThe whole value can be up to 20,000 bytes as compact JSON, nest up to 8 levels deep, and hold up to 200 keys in total. Lists can hold up to 50 items, keys up to 200 characters, and strings up to 2,000 characters. The keys `__proto__`, `constructor`, and `prototype` aren't allowed.","example":{"create":true,"read":{"created_by":"{{user.email}}"},"update":{"created_by":"{{user.email}}"},"delete":{"$or":[{"created_by":"{{user.email}}"},{"user_condition":{"role":"admin"}}]}}}}},"example":{"applied_rule":{"create":true,"read":{"created_by":"{{user.email}}"},"update":{"created_by":"{{user.email}}"},"delete":{"$or":[{"created_by":"{{user.email}}"},{"user_condition":{"role":"admin"}}]}}}}},"required":false},"responses":{"200":{"description":"The recommendation left the scan result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecurityRlsRecommendationDismissed"}}}},"400":{"description":"`entity_name` isn't a valid entity name."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"409":{"description":"The latest scan result has no recommendation for this entity, so nothing was recorded. The detail is `RLS recommendation was not dismissed. Refresh scan results and try again.`"},"422":{"description":"The body isn't a JSON object, has fields other than `applied_rule`, or `applied_rule` breaks one of the limits described on the field."},"429":{"description":"Too many dismissals for this app in the last minute."}}}},"/api/apps/{app_id}/security/scan/rls/fix":{"post":{"summary":"Fix RLS recommendations","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nApplies the row-level security rules the app's latest security scan recommends, for the entities you name. Get the names from `rls_recommendations` in [Get security scan](/api-reference/get-security-scan).\n\nFor each entity, Base44 replaces the entity's `rls` with the recommendation's create, read, update, and delete rules, exactly as the scan stored them. An operation the recommendation has no rule for is left with no rule, which doesn't restrict it. It doesn't run the scan again or ask an AI model anything, and it uses no credits. The applied recommendations then move into the scan's resolved history, the same way [Dismiss RLS recommendation](/api-reference/dismiss-rls-recommendation) records a fix.\n\nAn entity with nothing to apply doesn't fail the call. It's listed in `failed` with the reason, and the rest are still applied. Those are saved in one step, so if saving fails, every one of them is listed in `failed` as `write_failed`. Some of their rules can still have changed in that case, so read the entities' schemas before you retry. Read both lists rather than treating a successful response as every entity fixed.\n\nBy default the fix also shows up in the app's AI chat, with a checkpoint of the app's code saved just before it, so you can roll it back from there. Set `record_security_fix_chat` to `false` to skip both.\n\nThis is limited to 30 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"fix_rls_recommendations_api_apps__app_id__security_scan_rls_fix_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/RlsFixRequest"}}}},"responses":{"200":{"description":"Which entities got their recommended rules, and which didn't.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RlsFixResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"409":{"description":"A security scan is running on the app, the builder is working on it (error code `builder_busy`), or the app changed since its last scan (error code `scan_out_of_date`, only when fixes require a fresh scan for the caller). Apply the fixes once the scan or the builder finishes, or run a new scan first."},"422":{"description":"The body is missing or has fields other than `entity_names`, `record_security_fix_chat`, and `security_fix_total_count`, `entity_names` is empty or holds more than 100 names, or a name has a character other than a letter, digit, or underscore."},"429":{"description":"Too many fix requests for this app in the last minute."}}}},"/api/apps/{app_id}/security/scan/header-recommendations/{flag}":{"delete":{"summary":"Dismiss header recommendation","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 security header recommendation from the app's security scan result, without changing the app's headers. Get the `flag` from `header_recommendations` in [Get security scan](/api-reference/get-security-scan).\n\nThe recommendation stays hidden until the next time the app is scanned with [Run security scan](/api-reference/run-security-scan). That scan judges the app's headers again, so the recommendation comes back if it still applies.\n\nThe call succeeds without changing anything when the app has never been scanned, when the recommendation is already dismissed, or when `flag` isn't one of the two recommendations. To act on the recommendation instead, turn the setting on with [Update security headers](/api-reference/update-security-headers).\n\nThis is limited to 30 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"dismiss_header_recommendation_api_apps__app_id__security_scan_header_recommendations__flag__delete","parameters":[{"name":"flag","in":"path","required":true,"schema":{"type":"string","description":"The recommendation to dismiss, as returned in `header_recommendations[].flag` by [Get security scan](/api-reference/get-security-scan). Either `prevent_iframe_embedding` or `restrict_browser_features`.","title":"Flag"},"description":"The recommendation to dismiss, as returned in `header_recommendations[].flag` by [Get security scan](/api-reference/get-security-scan). Either `prevent_iframe_embedding` or `restrict_browser_features`.","example":"prevent_iframe_embedding"},{"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 recommendation is dismissed, or there was nothing to dismiss.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecurityHeaderRecommendationDismissed"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"429":{"description":"Too many dismissals for this app in the last minute."}}}},"/api/apps/{app_id}/security/headers":{"get":{"summary":"Get security headers","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 security header settings, and the framing policy the published app follows once its workspace's policy is applied.\n\nChange `prevent_iframe_embedding` and `restrict_browser_features` with [Update security headers](/api-reference/update-security-headers). `effective_policy` is what the published app actually enforces, so read it rather than working the policy out from the other fields. When `app_allowlist_locked_by_workspace` is `true`, the workspace's policy decides who can frame the app.\n\nThis is limited to 60 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"get_security_headers_settings_api_apps__app_id__security_headers_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":"The app's security header settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecurityHeadersSettingsResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded. The base limit is 60 requests per minute per app. Wait the number of seconds in the `Retry-After` header before you retry."}}},"put":{"summary":"Update security headers","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 app's security header settings and returns them as they now stand.\n\nSend only the settings you want to change. A setting you leave out keeps its value.\n\nA change applies to the published app right away, with no need to deploy it again. It doesn't affect the builder's preview.\n\nTurning on a setting that [Get security scan](/api-reference/get-security-scan) recommends in `header_recommendations` removes that recommendation from the scan result.\n\nThis is limited to 30 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"update_security_headers_settings_api_apps__app_id__security_headers_put","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":{"title":"UpdateSecurityHeaders","type":"object","properties":{"prevent_iframe_embedding":{"type":"boolean","description":"Set `true` to stop every site from showing the published app in a frame. Set `false` to lift that block, so framing follows the app's other settings and its workspace's policy again.","example":true},"restrict_browser_features":{"type":"boolean","description":"Set `true` to make the published app send a restrictive `Permissions-Policy` header, or `false` to stop sending it.","example":true}}},"example":{"prevent_iframe_embedding":true}}}},"responses":{"200":{"description":"The app's security header settings after the change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecurityHeadersSettingsResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"422":{"description":"The body is missing or isn't a JSON object, has an unknown field, or has a value that can't be read as a boolean."},"429":{"description":"Too many updates for this app in the last minute."}}}},"/api/apps/{app_id}/seo/scan":{"post":{"summary":"Run SEO 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 SEO problems and returns the [score and checklist](/developers/references/apps-api/sections/seo#the-score-and-the-checklist) in two parts. The score is a single 0-100 number with a letter grade. The checklist is the individual checks, each pass, warn, or fail, that produced the score.\n\nThe scan reads the app's own configuration and also fetches the live site over HTTP to check what it really serves for `robots.txt`, `sitemap.xml` and the home page. Those three run in parallel with a five-second timeout each and one retry, so expect a few seconds, and up to about ten when the live site is slow to answer.\n\nAn app that isn't published yet still scans, but its live checks have nothing to fetch. Rather than reporting every check that fails as a result, Base44 reports only the root cause and drops the downstream failures that just restate it, in this case that the app isn't published. The same collapsing applies when the live site's host can't be reached at all. Instead of one warning per probe, you get a single row for the host being unreachable.\n\nBase44 also stores the result, so [Get SEO score](/api-reference/get-seo-score) and [Get last SEO scan](/api-reference/get-last-seo-scan) return this same scan until the next one runs, without needing to scan again. Storing the result is best-effort and can't fail the scan itself, so a successful call here only promises the scan that just ran, not that the two report endpoints already reflect it. Read them back if you need to be sure they're in sync.\n\nThe score and the checklist are stored separately, and two scans running at once can interleave those two writes. So if you read both report endpoints, compare their `scanned_at` before treating the numbers as one scan's.\n\nEach check that Base44 can act on carries a `fix_action`, which you can apply with [Apply SEO fix](/api-reference/apply-seo-fix).\n\nThe overall score isn't a simple tally of passing and failing checks. Category scores are weighted differently, and some critical checks can further cap the overall score when they fail. That's why `score.overall` doesn't add up to a plain count of failing checks, even though the `passed`, `warnings` and `failures` counts inside each category are exact. Use `failures` from [Get SEO score](/api-reference/get-seo-score) if you need the exact total across every category.\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_seo_scan_api_apps__app_id__seo_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":"include_ai_visibility","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Ai Visibility"}}],"responses":{"200":{"description":"The scan Base44 just ran.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SEOScanReportResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, you used a workspace API key, or you used a read-only personal access token. These endpoints accept a user's credentials only, and none of them accept a read-only credential, including the ones that only read."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute for each app, counted across the workspace rather than per caller, and shared with the app's other SEO endpoints. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/seo/fix":{"post":{"summary":"Apply SEO fix","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nApplies the fix for one check from an SEO scan and returns what it did.\n\nGet the checks from [Run SEO scan](/api-reference/run-seo-scan) or [Get last SEO scan](/api-reference/get-last-seo-scan). Each check Base44 can act on carries a `fix_action`. Send its `type` as `fix_type` and its `params` as `params`.\n\nThe [fix actions](/developers/references/apps-api/sections/seo#fix-actions) fall into three groups. Some change the app's SEO settings, some change the app itself, and the rest change nothing and point you to the app editor screen where you make the fix. An action that changes settings replaces the value it sets, including a value someone edited by hand.\n\nNo action edits the app's code, creates a checkpoint, or uses the workspace's credits.\n\nA call can succeed and still report `success` as `false` when the action had nothing to apply, such as `generate_json_ld` on an app with no entity Base44 can match to a Schema.org type.\n\nCalling an action again applies it again. Most land on the same result every time, but `generate_description` writes a new description on each call.\n\nA successful call means the change is saved. The live site can take a little while to catch up.\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 `fix_type` values documented here. Other values are not supported and their behavior can change.</Warning>","operationId":"apply_seo_fix_api_apps__app_id__seo_fix_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to fix.","title":"App Id"},"description":"ID of the app to fix.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"ApplySEOFix","type":"object","required":["fix_type"],"properties":{"fix_type":{"type":"string","enum":["enable_llms_txt","enable_ai_crawlers","generate_json_ld","generate_page_meta","generate_website_schema","generate_organization_schema","generate_faq_schema","generate_breadcrumb_schema","generate_canonical","generate_description","suggest_shorter_title","set_app_title","edit_app_description","edit_public_access","open_seo_advanced","set_og_image","connect_domain","edit_slug","enable_https","publish_app","fix_with_ai"],"description":"Action to apply, the `type` of a check's `fix_action` in [Run SEO scan](/api-reference/run-seo-scan) or [Get last SEO scan](/api-reference/get-last-seo-scan). See [Fix actions](/developers/references/apps-api/sections/seo#fix-actions) for what each one does.","example":"enable_llms_txt"},"params":{"type":"object","additionalProperties":true,"default":{},"description":"Arguments for the action, the `params` of the same `fix_action`, sent as they are. Defaults to an empty object.","example":{}}}},"example":{"fix_type":"enable_llms_txt","params":{}}}}},"responses":{"200":{"description":"What the action did.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SEOFixResponse"}}}},"400":{"description":"`fix_type` isn't an action Base44 knows."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, you used a workspace API key, or you used a read-only personal access token. These endpoints accept a user's credentials only, and none of them accept a read-only credential, including the ones that only read."},"404":{"description":"App not found."},"422":{"description":"The body is missing or isn't a JSON object, `fix_type` is missing or isn't a string, or `params` isn't an object."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute for each app, counted across the workspace rather than per caller, and shared with the app's other SEO endpoints. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/seo/settings":{"get":{"summary":"Get SEO settings","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 SEO settings.\n\nOnly the keys you've actually saved appear in `result`, since it's the stored settings document itself rather than a computed view of them. An app whose SEO settings have never been changed returns an empty object, so treat a missing key as the default documented on that field rather than as unset.\n\nChange them with [Update SEO settings](/api-reference/update-seo-settings).\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_seo_settings_api_apps__app_id__seo_settings_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose SEO settings you want.","title":"App Id"},"description":"ID of the app whose SEO settings you want.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The app's saved SEO settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SEOSettingsResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, you used a workspace API key, or you used a read-only personal access token. These endpoints accept a user's credentials only, and none of them accept a read-only credential, including the ones that only read."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute for each app, counted across the workspace rather than per caller, and shared with the app's other SEO endpoints. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}},"put":{"summary":"Update SEO settings","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSaves SEO settings for the app and returns the settings as they now stand.\n\nThis is a partial update. Keys you leave out of `settings` keep their saved values. The same is true one level deeper for `pages`. A page absent from your `pages` object keeps its existing overrides, and within a page you do include, any field you leave out keeps its saved value. For example, sending only a page's `title` changes that field alone. Its `description` and every other page stay as they were. To hide or show a single page, [Update page SEO settings](/api-reference/update-page-seo-settings) changes just its `noindex` without sending the rest of the settings.\n\nYou can't remove a page or a page override through this endpoint. An empty `pages` object changes nothing, and a `null` page entry is rejected. To stop a page overriding a tag, send an empty string as that field's value instead.\n\nA successful call means the settings are saved. The live site can take a little while to catch up, so confirm with [Get SEO settings](/api-reference/get-seo-settings) rather than treating the published pages as proof.\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_seo_settings_api_apps__app_id__seo_settings_put","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose SEO settings you want.","title":"App Id"},"description":"ID of the app whose SEO settings you want.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"UpdateSEOSettings","type":"object","required":["settings"],"properties":{"settings":{"description":"The settings to save. Send only the keys you want to change.","properties":{"seo_disabled":{"description":"Whether you run your own SEO. When `true`, Base44 stops injecting titles, descriptions, Open Graph and Twitter tags, canonical URLs, JSON-LD and prerender snapshots, and serves your own `public/robots.txt` when you ship one. Defaults to `false` while the key is absent. This does not add a `noindex` tag, and a login-gated or private app stays unindexed whatever you set here.","example":false,"title":"Seo Disabled","type":"boolean"},"platform_meta_injection_enabled":{"description":"Whether Base44 injects the title, description, Open Graph and Twitter tag block. Set it to `false` when your deployed `index.html` already carries a complete set. Narrower than `seo_disabled`, which also turns off canonical URLs, JSON-LD and snapshots. Defaults to `true` while the key is absent.","example":true,"title":"Platform Meta Injection Enabled","type":"boolean"},"platform_jsonld_injection_enabled":{"description":"Whether Base44 injects its own WebSite, Organization, BreadcrumbList and FAQPage JSON-LD. Set it to `false` when your app ships its own structured data. Defaults to `true` while the key is absent.","example":true,"title":"Platform Jsonld Injection Enabled","type":"boolean"},"robots_txt_enabled":{"description":"Whether Base44 generates `/robots.txt`. When `false`, your deployed `public/robots.txt` is served instead, and the path returns 404 when you ship none. Defaults to `true` while the key is absent.","example":true,"title":"Robots Txt Enabled","type":"boolean"},"sitemap_xml_enabled":{"description":"Whether Base44 generates `/sitemap.xml`. When `false`, your deployed `public/sitemap.xml` is served instead, and the path returns 404 when you ship none. Defaults to `true` while the key is absent.","example":true,"title":"Sitemap Xml Enabled","type":"boolean"},"llms_txt_enabled":{"description":"Whether Base44 serves a generated `/llms.txt` describing the app to AI crawlers. Defaults to `false` while the key is absent.","example":false,"title":"Llms Txt Enabled","type":"boolean"},"ai_crawlers_enabled":{"description":"Whether known AI crawlers are allowed in the generated `robots.txt`. Defaults to `true` while the key is absent.","example":true,"title":"Ai Crawlers Enabled","type":"boolean"},"synthesized_breadcrumb_enabled":{"description":"Whether Base44 builds a BreadcrumbList for each request path at render time. Set it to `false` to serve the saved `breadcrumb_schema` instead, which is what keeps a hand-written breadcrumb intact. Defaults to `true` while the key is absent.","example":true,"title":"Synthesized Breadcrumb Enabled","type":"boolean"},"canonical_url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Base URL Base44 builds canonical tags from. The app's own published URL is used while the key is absent.","example":"https://acme.com","title":"Canonical Url"},"content_query_params":{"description":"Query-string parameter names whose values serve distinct content, such as `article` for `/guides?article=slug`. Naming one opts its URL variants into prerender warming, which otherwise only covers paths. Send at most 25 names, counted before the list is cleaned up, and match each name's casing exactly. Names are trimmed, and blank or repeated entries are dropped, so the list you read back can be shorter than the one you sent. Defaults to an empty list, meaning no query variants are warmed.","example":["article"],"items":{"type":"string"},"maxItems":25,"title":"Content Query Params","type":"array"},"custom_json_ld":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"description":"Extra JSON-LD entries Base44 injects alongside its own. Each entry needs a `json_ld_template` key holding the markup object to inject, matching the shape [Preview JSON-LD](/api-reference/preview-json-ld) returns. The key is absent while you have added none.","example":[{"confidence":"0.85","entity_name":"Product","field_mapping":{"name":"title"},"json_ld_template":{"@context":"https://schema.org","@type":"Product","name":"Acme CRM"},"schema_type":"Product"}],"title":"Custom Json Ld"},"website_schema":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"description":"Saved WebSite JSON-LD object. The key is absent while none is saved.","example":{"@context":"https://schema.org","@type":"WebSite","name":"Acme CRM","url":"https://acme.com"},"title":"Website Schema"},"organization_schema":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"description":"Saved Organization JSON-LD object. The key is absent while none is saved.","example":{"@context":"https://schema.org","@type":"Organization","name":"Acme","url":"https://acme.com"},"title":"Organization Schema"},"breadcrumb_schema":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"description":"Saved BreadcrumbList JSON-LD object. It is served only while `synthesized_breadcrumb_enabled` is `false`. The key is absent while none is saved.","example":{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[]},"title":"Breadcrumb Schema"},"faq_schema":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"description":"Saved FAQPage JSON-LD object. The key is absent while none is saved.","example":{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[]},"title":"Faq Schema"},"pages":{"additionalProperties":{"description":"The meta tags one page overrides, and whether it's hidden from search engines.","properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Title tag for this page. The key is absent while the page overrides no title.","example":"Acme CRM","title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Meta description for this page. The key is absent while the page overrides no description.","example":"Track leads and deals in one place.","title":"Description"},"og_image":{"anyOf":[{"maxLength":2048,"type":"string"},{"type":"null"}],"description":"Image URL for the `og:image` and `twitter:image` tags on this page. It must be an `https://` URL with a host and no backslashes, and it cannot exceed 2048 characters. Send an empty string to stop overriding the image.","example":"https://cdn.example.com/acme-og.png","title":"Og Image"},"noindex":{"description":"Whether the page is hidden from search engines. When `true`, Base44 serves the page with a `noindex, nofollow` robots directive and leaves it out of the `sitemap.xml` it generates. This applies only while the app is search-eligible and `seo_disabled` is `false`. Defaults to `false` while the key is absent.","example":false,"title":"Noindex","type":"boolean"}},"title":"SEOPageSettings","type":"object"},"description":"Per-page tag overrides, keyed by the page's component name as it appears in the app's code. The key is absent while no page overrides anything.","example":{"Home":{"description":"Track leads and deals in one place.","og_image":"https://cdn.example.com/acme-og.png","title":"Acme CRM"}},"title":"Pages","type":"object"}},"title":"SEOSettingsUpdate","type":"object"}}},"example":{"settings":{"pages":{"Home":{"title":"Acme CRM","description":"Track leads and deals in one place.","og_image":"https://cdn.example.com/acme-og.png"}},"sitemap_xml_enabled":true}}}}},"responses":{"200":{"description":"The settings as they stand after the merge.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SEOSettingsResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, you used a workspace API key, or you used a read-only personal access token. These endpoints accept a user's credentials only, and none of them accept a read-only credential, including the ones that only read."},"404":{"description":"App not found."},"422":{"description":"The body is missing, `settings` is missing or not an object, a value doesn't match its field's type, `og_image` exceeds 2048 characters or isn't a valid `https://` URL with a host, `content_query_params` has more than 25 entries, or an entry in `custom_json_ld` isn't a JSON object."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute for each app, counted across the workspace rather than per caller, and shared with the app's other SEO endpoints. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/seo/page-visibility":{"get":{"summary":"Get page visibility","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 pages that a hidden parent page keeps out of search engines, using the same rule the published app applies.\n\nHiding a page with `noindex` also hides the pages under it, so hiding `Units` hides `/units/:id`. Keep one of those pages visible by setting `ignore_parent_noindex` to `true` on it with [Update page SEO settings](/api-reference/update-page-seo-settings). It then appears here with `ignored` set to `true`.\n\nA page that's also hidden by its own `noindex` is listed when a parent would hide it too, because turning its own `noindex` off doesn't make it visible. A page can appear under two keys, its file path (`units/UnitDetail`) and its component name (`UnitDetail`), with the same entry under both.","operationId":"get_page_visibility_api_apps__app_id__seo_page_visibility_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose SEO settings you want.","title":"App Id"},"description":"ID of the app whose SEO settings you want.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The pages a hidden parent page covers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SEOPageVisibilityResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, you used a workspace API key, or you used a read-only personal access token. These endpoints accept a user's credentials only, and none of them accept a read-only credential, including the ones that only read."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute for each app, counted across the workspace rather than per caller, and shared with the app's other SEO endpoints. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/seo/summary":{"get":{"summary":"Get SEO score","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 [score](/developers/references/apps-api/sections/seo#the-score-and-the-checklist) from the app's last SEO scan, without the checklist behind it.\n\nThis endpoint retrieves the score from a scan that already ran. It doesn't run a new scan itself. It returns `null` until the app has been scanned at least once, so run [Run SEO scan](/api-reference/run-seo-scan) first if you haven't. Use [Get last SEO scan](/api-reference/get-last-seo-scan) when you need the individual checks.\n\nThe score and the checklist are stored separately, and two scans running at once can interleave those two writes. So compare `scanned_at` with the one [Get last SEO scan](/api-reference/get-last-seo-scan) returns before treating the two as one scan's.\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_seo_scan_summary_api_apps__app_id__seo_summary_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":"The last scan's score, or `null` if the app has never been scanned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SEOScoreResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, you used a workspace API key, or you used a read-only personal access token. These endpoints accept a user's credentials only, and none of them accept a read-only credential, including the ones that only read."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute for each app, counted across the workspace rather than per caller, and shared with the app's other SEO endpoints. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/seo/last-scan":{"get":{"summary":"Get last SEO 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 last SEO scan in full, its [score and checklist](/developers/references/apps-api/sections/seo#the-score-and-the-checklist) together.\n\nThis returns the same score and checklist [Run SEO scan](/api-reference/run-seo-scan) returned when it ran, so poll this rather than re-scanning to re-read a result. Anything else that scan's response carried isn't persisted, so it never appears here. It returns `null` until the app is scanned at least once, and the result is only as current as the last scan, so read `scanned_at` before acting on it.\n\nThe score and the checklist are stored separately, and two scans running at once can interleave those two writes. So compare `scanned_at` with the one [Get SEO score](/api-reference/get-seo-score) returns before treating the two as one scan's.\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_last_seo_scan_api_apps__app_id__seo_last_scan_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":"The last scan, or `null` if the app has never been scanned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LastSEOScanResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, you used a workspace API key, or you used a read-only personal access token. These endpoints accept a user's credentials only, and none of them accept a read-only credential, including the ones that only read."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute for each app, counted across the workspace rather than per caller, and shared with the app's other SEO endpoints. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/seo/page-settings/{page_name}":{"patch":{"summary":"Update page SEO settings","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 page from search engines or makes it visible again, and returns that page's settings as they now stand.\n\nThis endpoint changes only `noindex` and `ignore_parent_noindex`. The page's other fields, and every other page, keep their saved values. See which pages a hidden parent page covers with [Get page visibility](/api-reference/get-page-visibility). Change a page's title, description, or image with [Update SEO settings](/api-reference/update-seo-settings), which can also set `noindex` on several pages in one call.\n\nA successful call means the setting is saved. The live site can take a little while to catch up.\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":"patch_page_settings_api_apps__app_id__seo_page_settings__page_name__patch","parameters":[{"name":"page_name","in":"path","required":true,"schema":{"type":"string","description":"Name of the page, the same key it has in `pages` in [Get SEO settings](/api-reference/get-seo-settings). That's the page's component name as it appears in the app's code, with any folder under `pages` in front of it, such as `About` or `services/HouseWashing`. The home page goes by its own name, such as `Home`, not `/`. The name is case-sensitive, and you can send its slashes as they are or encoded as `%2F`.","title":"Page Name"},"description":"Name of the page, the same key it has in `pages` in [Get SEO settings](/api-reference/get-seo-settings). That's the page's component name as it appears in the app's code, with any folder under `pages` in front of it, such as `About` or `services/HouseWashing`. The home page goes by its own name, such as `Home`, not `/`. The name is case-sensitive, and you can send its slashes as they are or encoded as `%2F`.","example":"services/HouseWashing"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose SEO settings you want.","title":"App Id"},"description":"ID of the app whose SEO settings you want.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"UpdatePageSettings","type":"object","additionalProperties":false,"properties":{"noindex":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Set to `true` to hide the page from search engines, or `false` to make it visible again. Leaving it out or sending `null` keeps the saved value and returns the page's current settings.","example":true},"ignore_parent_noindex":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Set to `true` to keep this page visible to search engines when a parent page is hidden (e.g. `/units/:id` while `Units` is hidden). It never overrides the page's own `noindex`.","example":true}}},"example":{"noindex":true}}}},"responses":{"200":{"description":"The page's settings after the change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SEOPageSettingsResponse"}}}},"400":{"description":"The page name is empty or only whitespace, is longer than 200 characters, or starts with `$`."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, you used a workspace API key, or you used a read-only personal access token. These endpoints accept a user's credentials only, and none of them accept a read-only credential, including the ones that only read."},"404":{"description":"The app doesn't exist, or it has no page by that name and no saved settings under it."},"422":{"description":"The body is missing or isn't a JSON object, it carries a field other than `noindex` and `ignore_parent_noindex`, or one of them can't be read as a boolean."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute for each app, counted across the workspace rather than per caller, and shared with the app's other SEO endpoints. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/seo/preview/llms-txt":{"get":{"summary":"Preview llms.txt","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 preview of `llms.txt`, the plain-text summary of the app Base44 generates for AI crawlers, so you can see it before publishing it.\n\nThis endpoint generates a fresh version every time you call it, from the app's current draft, the name, description and pages you're working on, not what's already published live.\n\nThis preview always generates content, regardless of the app's publish state, search eligibility, or the `llms_txt_enabled` and `seo_disabled` toggles. It does still leave out a page you've marked `noindex`. Whether the live `/llms.txt` path serves this same content depends on separate rules. See [When llms.txt goes live](/developers/references/apps-api/sections/seo#when-llms-txt-goes-live) for those.\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":"preview_llms_txt_api_apps__app_id__seo_preview_llms_txt_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":"The generated `llms.txt`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LlmsTxtPreviewResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, you used a workspace API key, or you used a read-only personal access token. These endpoints accept a user's credentials only, and none of them accept a read-only credential, including the ones that only read."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute for each app, counted across the workspace rather than per caller, and shared with the app's other SEO endpoints. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/seo/preview/json-ld":{"get":{"summary":"Preview JSON-LD","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 Schema.org markup Base44 generates from the app's entities, so you can see it before publishing it.\n\nBase44 matches each entity to the Schema.org type its fields look most like, and leaves out any entity it can't match with reasonable confidence, so an app whose entities are all app-specific returns an empty list.\n\nEach entry's `json_ld_template` marks which entity field fills each property using a placeholder. For example, `{\"name\": \"{title}\"}` means `name` comes from this entity's `title` field.\n\nThere's no mechanism that fills in a placeholder for you. To use a field, edit `json_ld_template` yourself before saving it with [Update SEO settings](/api-reference/update-seo-settings), replacing `{title}` with an actual value, for example `{\"name\": \"Acme CRM\"}` instead of `{\"name\": \"{title}\"}`. Base44 injects whatever you save exactly as written, on every page the entry applies to. There's no per-record substitution, so a hardcoded value shows up identically everywhere rather than varying by record.\n\nSave the whole entry, including its `json_ld_template` key, as one of the objects in `custom_json_ld` with [Update SEO settings](/api-reference/update-seo-settings). Saving alone isn't enough for the markup to appear on the page. See [When JSON-LD markup goes live](/developers/references/apps-api/sections/seo#when-json-ld-markup-goes-live) for what else has to be true.\n\nSave `confidence` as a string, or leave it out.\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":"preview_json_ld_api_apps__app_id__seo_preview_json_ld_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":"The generated Schema.org markup.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonLdPreviewResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, you used a workspace API key, or you used a read-only personal access token. These endpoints accept a user's credentials only, and none of them accept a read-only credential, including the ones that only read."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute for each app, counted across the workspace rather than per caller, and shared with the app's other SEO endpoints. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/access-requests/all":{"get":{"summary":"List access requests","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 people who asked to join the app and the people invited to it who haven't joined yet, newest first.\n\nCheck `status` on each entry. `pending` means the person asked for access and is waiting for you to [approve or deny](/api-reference/approve-or-deny-access-request) it. `approved` means they're invited or approved but haven't joined yet. `completed` means they've joined: the entry stays while they're one of the app's users, and they also appear in [List app users](/api-reference/list-app-users). Requests still waiting for the person to confirm their email don't appear.\n\nSet `role` to list one role only. Every matching entry comes back at once, with no paging.\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":"list_access_requests_api_apps__app_id__access_requests_all_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":"role","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"List only entries with this app role. `all`, or leaving it out, lists every role.","title":"Role"},"description":"List only entries with this app role. `all`, or leaving it out, lists every role.","example":"user"}],"responses":{"200":{"description":"The app's access requests and pending invitations.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AccessRequestSummary"},"title":"AccessRequests"}}}},"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."}}}},"/api/apps/{app_id}/access-requests/{request_id}":{"put":{"summary":"Update pending invitee","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSets custom `User` fields on someone who hasn't joined the app yet. When they join, the values are copied onto their user record.\n\nThe fields you send are merged into the stored ones, and a field you leave out keeps its value. Built-in fields such as `email`, `full_name`, `role` and `id` are ignored. Field-level security rules on the `User` entity apply, and a value over 20,000 characters is refused. Once the person has joined, change them with [Update app user](/api-reference/update-app-user) instead, because this endpoint returns a 404 for them.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"update_access_request_api_apps__app_id__access_requests__request_id__put","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the access request, as `id` in the response of [List access requests](/api-reference/list-access-requests).","title":"Request Id"},"description":"ID of the access request, as `id` in the response of [List access requests](/api-reference/list-access-requests).","example":"68d4a1f7c2b9e5001a7f3c60"},{"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/UpdateAccessRequestPayload"}}}},"responses":{"200":{"description":"The invitation's custom fields after the update.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatedInvitation"}}}},"400":{"description":"A field value is over 20,000 characters."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, a field-level security rule refuses a field you're changing, or your API key is read-only."},"404":{"description":"App not found, or the app has no pending invitation with this ID, including one the person already accepted."},"422":{"description":"`data` is missing, or isn't a JSON object."}}}},"/api/apps/{app_id}/access-requests/{request_id}/{action}":{"post":{"summary":"Approve or deny access request","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 denies someone's request to join the app.\n\nApproving a request from someone who confirmed their email adds them to the app's users with the request's role, and emails them that they're in. A request made before email confirmation existed is marked `approved` instead, and the person joins when they next sign in. Someone who signed up with a password can't be approved until they confirm their email. Approving an entry that's already `approved` or `completed` sends the approval email again.\n\nDenying deletes the request without notifying the person, and denying an invitation withdraws it. Denying a `completed` entry deletes only the entry; remove the person with [Remove app user](/api-reference/remove-app-user).\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"review_access_request_api_apps__app_id__access_requests__request_id___action__post","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the access request, as `id` in the response of [List access requests](/api-reference/list-access-requests).","title":"Request Id"},"description":"ID of the access request, as `id` in the response of [List access requests](/api-reference/list-access-requests).","example":"68d4a1f7c2b9e5001a7f3c60"},{"name":"action","in":"path","required":true,"schema":{"enum":["approve","deny"],"type":"string","description":"`approve` to let the person in, or `deny` to delete the request.","title":"Action"},"description":"`approve` to let the person in, or `deny` to delete the request.","example":"approve"},{"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 reviewed access request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewAccessRequestResponse"}}}},"400":{"description":"You're approving someone who signed up with a password and hasn't confirmed their email yet."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or your API key is read-only."},"404":{"description":"App not found, or the app has no access request with this ID."},"422":{"description":"`action` isn't `approve` or `deny`."}}}},"/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 editor access to this app, you used a read-only personal access token, or you used a workspace API key."},"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 editor access to this app, you used a read-only personal access token, or you used a workspace API key."},"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 editor access to this app, you used a read-only personal access token, or you used a workspace API key."},"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 editor access to this app, you used a read-only personal access token, or you used a workspace API key."},"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. Use it to rank things such as the busiest pages, the countries your users come from, or 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 editor access to this app, you used a read-only personal access token, or you used a workspace API key."},"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 editor access to this app, you used a read-only personal access token, or you used a workspace API key."},"404":{"description":"App not found."}}}},"/api/apps/{app_id}/analytics/suggestions":{"get":{"summary":"Suggest analytics events to track","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns short prompts that suggest analytics events worth tracking in the app.\n\nAn AI model writes the prompts from the app's name, categories, description, and recent AI chat messages, so each call can return different ones.\n\nThe prompts are always in English, and you usually get four. If the model fails to produce them, you get a fixed set of general prompts instead, with a successful response. The call doesn't use the workspace's credits.\n\nTo have the AI add event tracking to the app, put `prefix` in front of a prompt and send it with [Send chat message](/api-reference/send-chat-message). Once the app is tracking events, read them with [List analytics event names](/api-reference/list-analytics-event-names).","operationId":"get_event_suggestions_api_apps__app_id__analytics_suggestions_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":"Suggested prompts for the app.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestionsResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you used a read-only personal access token, or you used a workspace API key."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute, shared by all of the workspace's apps and personal access tokens. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/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}/sandbox-bridge/run_command":{"post":{"summary":"Run a sandbox command","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 shell command inside the app's sandbox with bash and returns its output.\n\nEvery sandbox-bridge endpoint runs against the app's live sandbox, the same filesystem the AI edits, so a change here is visible in the app editor immediately.\n\nA command that exits non-zero still answers 200. Read `exit_code` to tell a failed command from a failed request, and expect `stderr` to carry output on success too, since many tools log there.\n\nEach call starts in the app root unless you set `cwd`, and `cd` does not carry over between calls, so chain directory changes inside one command instead. The default timeout is 120000 ms and the maximum is 600000 ms; a command that outruns its timeout answers 504. Output is capped at 1 MB per stream, and `truncated` tells you when that happened.\n\nThis endpoint is limited to 30 requests per minute per app, its own budget rather than one shared with the other sandbox-bridge endpoints.\n\n<Warning>A write is committed, not checkpointed. Only checkpoints appear in the app editor's version history, and a Restore or Revert there rolls the app back to the last checkpoint and discards everything written after it. Call [Create a sandbox checkpoint](/api-reference/create-a-sandbox-checkpoint) when you finish a unit of work and before you stop.</Warning>\n\n<Note>The sandbox bridge needs a Builder plan or higher on the app's workspace, and answers 402 below that. Workspace API keys are not authorized and are rejected with a 403, and it is unavailable for agent apps. A personal API key works as-is. An OAuth access token needs the `sandbox:write` scope.</Note>\n\n<Tip>Every error response carries a stable `extra_data.code` alongside the human-readable `message`. Branch on the code rather than on the message text or the status.</Tip>","operationId":"run_command_endpoint_api_apps__app_id__sandbox_bridge_run_command_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose sandbox to operate on.","title":"App Id"},"description":"ID of the app whose sandbox to operate on.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"default":{},"title":"RunSandboxCommand","properties":{"branch_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional Base44 branch ID. Omit to operate on main.","title":"Branch Id"},"command":{"description":"Shell command to execute via bash inside the sandbox.","maxLength":100000,"minLength":1,"title":"Command","type":"string"},"cwd":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Working dir relative to the app root. `cd` does not persist across calls — use this or chain commands.","title":"Cwd"},"timeout_ms":{"default":120000,"description":"Timeout in milliseconds (default 120000, max 600000).","maximum":600000,"minimum":1,"title":"Timeout Ms","type":"integer"}},"required":["command"]},"example":{"command":"npm install","timeout_ms":300000}}},"required":true},"responses":{"200":{"description":"The command ran. Read `exit_code` for its result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunCommandResult"}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The app's workspace plan doesn't include the sandbox bridge."},"403":{"description":"You don't have access to this app, the app is blocked, your OAuth token is missing the scope this endpoint needs, or you used a workspace API key."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded (30 requests per minute)."},"409":{"description":"The app is on a branch that can't be written to: a protected main, or a branch that has been merged or closed."},"504":{"description":"The sandbox didn't answer in time. Retry the request."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/sandbox-bridge/read_file":{"post":{"summary":"Read app files","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 or more files from the app's sandbox.\n\nEvery sandbox-bridge endpoint runs against the app's live sandbox, the same filesystem the AI edits, so a change here is visible in the app editor immediately.\n\nAsk for up to 50 paths in one call. Set `offset` and `limit` to read a line range instead of whole files, which is what keeps a large file inside the response budget. A successful entry reports `total_lines` and `truncated`, so you can tell a partial read from a complete one.\n\n**A path that fails does not fail the request.** Every per-path problem rides inside the 200 as an `error` on that entry: a file that doesn't exist, one that isn't UTF-8 text, a path outside the app or in a protected tree, the point where the batch exhausts its aggregate read budget, and a read the sandbox itself refused. So a mixed response is normal, and a request where every path failed is still a 200. Check each entry for `error` before reading `content`, and branch on `error.code`.\n\nThis is a read, so a viewer on the workspace can call it and a read-only branch is no obstacle.\n\nThis endpoint is limited to 120 requests per minute per app, shared with the other sandbox-bridge endpoints that only read.\n\n<Note>The sandbox bridge needs a Builder plan or higher on the app's workspace, and answers 402 below that. Workspace API keys are not authorized and are rejected with a 403, and it is unavailable for agent apps. A personal API key works as-is. An OAuth access token needs the `apps:read` scope; the read endpoints don't require `sandbox:write`.</Note>\n\n<Tip>Every error response carries a stable `extra_data.code` alongside the human-readable `message`. Branch on the code rather than on the message text or the status.</Tip>","operationId":"read_file_endpoint_api_apps__app_id__sandbox_bridge_read_file_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose sandbox to operate on.","title":"App Id"},"description":"ID of the app whose sandbox to operate on.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"default":{},"title":"ReadAppFiles","properties":{"branch_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional Base44 branch ID. Omit to operate on main.","title":"Branch Id"},"paths":{"description":"One or more file paths relative to the app root.","items":{"type":"string"},"maxItems":50,"minItems":1,"title":"Paths","type":"array"},"offset":{"anyOf":[{"minimum":1,"type":"integer"},{"type":"null"}],"description":"1-based start line (optional).","title":"Offset"},"limit":{"anyOf":[{"minimum":1,"type":"integer"},{"type":"null"}],"description":"Max lines to return from offset (optional).","title":"Limit"}},"required":["paths"]},"example":{"paths":["src/pages/Home.jsx","package.json"]}}},"required":true},"responses":{"200":{"description":"One entry per requested path, each either a read or an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadFileResult"}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The app's workspace plan doesn't include the sandbox bridge."},"403":{"description":"You don't have access to this app, the app is blocked, your OAuth token is missing the scope this endpoint needs, or you used a workspace API key."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded (120 requests per minute)."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/sandbox-bridge/write_file":{"post":{"summary":"Write an app file","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 whole file into the app's sandbox, creating it or replacing it.\n\nEvery sandbox-bridge endpoint runs against the app's live sandbox, the same filesystem the AI edits, so a change here is visible in the app editor immediately.\n\nThe default refuses to clobber: writing a path that already exists answers 409 unless you send `overwrite: true`. Read `created` and `overwritten` on the response to see which happened.\n\n`content` cannot be empty. The app editor's write path reads empty content as a delete, so this endpoint rejects it rather than deleting a file behind a write call. To create an empty file, run `touch` through [Run a sandbox command](/api-reference/run-a-sandbox-command); to change part of a file, use [Edit an app file](/api-reference/edit-an-app-file), which is cheaper than sending the whole thing.\n\nBase44 refuses paths outside the app and a set of protected paths, both with a 400. A file over the 6 MB cap answers 413.\n\nAn entity file is named after its entity: `base44/entities/TeamMember.jsonc` defines `TeamMember`. A file that would define a new entity whose name isn't letters, numbers and underscores answers 400 with `INVALID_ENTITY_NAME`, since it would never become a table; the message names the file to write instead.\n\nThis endpoint is limited to 60 requests per minute per app, shared with the other sandbox-bridge endpoints that change files.\n\n<Warning>A write is committed, not checkpointed. Only checkpoints appear in the app editor's version history, and a Restore or Revert there rolls the app back to the last checkpoint and discards everything written after it. Call [Create a sandbox checkpoint](/api-reference/create-a-sandbox-checkpoint) when you finish a unit of work and before you stop.</Warning>\n\n<Note>The sandbox bridge needs a Builder plan or higher on the app's workspace, and answers 402 below that. Workspace API keys are not authorized and are rejected with a 403, and it is unavailable for agent apps. A personal API key works as-is. An OAuth access token needs the `sandbox:write` scope.</Note>\n\n<Tip>Every error response carries a stable `extra_data.code` alongside the human-readable `message`. Branch on the code rather than on the message text or the status.</Tip>","operationId":"write_file_endpoint_api_apps__app_id__sandbox_bridge_write_file_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose sandbox to operate on.","title":"App Id"},"description":"ID of the app whose sandbox to operate on.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"default":{},"title":"WriteAppFile","properties":{"branch_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional Base44 branch ID. Omit to operate on main.","title":"Branch Id"},"path":{"description":"File path relative to the app root.","title":"Path","type":"string"},"content":{"description":"Full file content to write. To create an empty file (e.g. .gitkeep) use run_command (touch) — the platform write path treats empty content as a delete.","maxLength":6000000,"minLength":1,"title":"Content","type":"string"},"overwrite":{"default":false,"description":"Must be true to overwrite an existing file (destructive). Default false never clobbers.","title":"Overwrite","type":"boolean"}},"required":["path","content"]},"example":{"path":"src/pages/About.jsx","content":"export default function About() {\n  return <h1>About</h1>;\n}\n"}}},"required":true},"responses":{"200":{"description":"The file was written.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteFileResult"}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The app's workspace plan doesn't include the sandbox bridge."},"403":{"description":"You don't have access to this app, the app is blocked, your OAuth token is missing the scope this endpoint needs, or you used a workspace API key."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded (60 requests per minute)."},"400":{"description":"The path points outside the app or at a protected path, or it is an entity file that would define an invalid entity name (`INVALID_ENTITY_NAME`)."},"409":{"description":"The file exists and you didn't send `overwrite`, the app is on a branch that can't be written to, or an earlier change is still being committed."},"413":{"description":"The content is larger than the 6 MB cap."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/sandbox-bridge/edit_file":{"post":{"summary":"Edit an app file","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nApplies exact-string edits to a file in the app's sandbox and returns the diff.\n\nEvery sandbox-bridge endpoint runs against the app's live sandbox, the same filesystem the AI edits, so a change here is visible in the app editor immediately.\n\nSend up to 100 edits and Base44 applies them in order, all or nothing: if any one fails to match, the file is left untouched and the request answers 400. Each `old_text` has to match exactly, and has to be unique in the file unless you set `replace_all` on that edit.\n\nSet `dry_run: true` to get the same `diff` back without writing. A dry run answers with `applied` as `false` and no `warnings` key at all, so read `applied` rather than checking for warnings. A dry run still needs write access, because it is the edit tool.\n\nPrefer this over [Write an app file](/api-reference/write-an-app-file) for changing part of a file: you send only the strings involved, and the response shows exactly what changed.\n\nAn entity file is named after its entity: `base44/entities/TeamMember.jsonc` defines `TeamMember`. A file that would define a new entity whose name isn't letters, numbers and underscores answers 400 with `INVALID_ENTITY_NAME`, since it would never become a table; the message names the file to write instead.\n\nThis endpoint is limited to 60 requests per minute per app, shared with the other sandbox-bridge endpoints that change files.\n\n<Warning>A write is committed, not checkpointed. Only checkpoints appear in the app editor's version history, and a Restore or Revert there rolls the app back to the last checkpoint and discards everything written after it. Call [Create a sandbox checkpoint](/api-reference/create-a-sandbox-checkpoint) when you finish a unit of work and before you stop.</Warning>\n\n<Note>The sandbox bridge needs a Builder plan or higher on the app's workspace, and answers 402 below that. Workspace API keys are not authorized and are rejected with a 403, and it is unavailable for agent apps. A personal API key works as-is. An OAuth access token needs the `sandbox:write` scope.</Note>\n\n<Tip>Every error response carries a stable `extra_data.code` alongside the human-readable `message`. Branch on the code rather than on the message text or the status.</Tip>","operationId":"edit_file_endpoint_api_apps__app_id__sandbox_bridge_edit_file_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose sandbox to operate on.","title":"App Id"},"description":"ID of the app whose sandbox to operate on.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"default":{},"title":"EditAppFile","properties":{"branch_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional Base44 branch ID. Omit to operate on main.","title":"Branch Id"},"path":{"description":"File path relative to the app root.","title":"Path","type":"string"},"edits":{"description":"Ordered exact-string edits. Applied all-or-nothing.","items":{"additionalProperties":false,"properties":{"old_text":{"description":"Exact text to find. Must be unique in the file unless replace_all is true. Cannot be empty.","minLength":1,"title":"Old Text","type":"string"},"new_text":{"description":"Replacement text.","title":"New Text","type":"string"},"replace_all":{"default":false,"description":"Replace every occurrence instead of requiring uniqueness.","title":"Replace All","type":"boolean"}},"required":["old_text","new_text"],"title":"_Edit","type":"object"},"maxItems":100,"minItems":1,"title":"Edits","type":"array"},"dry_run":{"default":false,"description":"Return the unified diff without writing.","title":"Dry Run","type":"boolean"}},"required":["path","edits"]},"example":{"path":"src/pages/Home.jsx","edits":[{"old_text":"<h1>Hello</h1>","new_text":"<h1>Welcome</h1>"}]}}},"required":true},"responses":{"200":{"description":"The diff, applied unless you asked for a dry run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditFileResult"}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The app's workspace plan doesn't include the sandbox bridge."},"403":{"description":"You don't have access to this app, the app is blocked, your OAuth token is missing the scope this endpoint needs, or you used a workspace API key."},"404":{"description":"App not found, or the file doesn't exist."},"429":{"description":"Rate limit exceeded (60 requests per minute)."},"400":{"description":"An `old_text` didn't match, matched more than once without `replace_all`, the edits would empty the file, the path points outside the app or at a protected path, or it is an entity file that would define an invalid entity name (`INVALID_ENTITY_NAME`)."},"409":{"description":"The app is on a branch that can't be written to: a protected main, or a branch that has been merged or closed."},"413":{"description":"The resulting file would be larger than the 6 MB cap."},"422":{"description":"The file is binary."}}}},"/api/apps/{app_id}/sandbox-bridge/grep":{"post":{"summary":"Search app files","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSearches the app's files for a pattern and returns the matching lines.\n\nEvery sandbox-bridge endpoint runs against the app's live sandbox, the same filesystem the AI edits, so a change here is visible in the app editor immediately.\n\nThe pattern is a regular expression by default; set `is_regex: false` to match it literally. Narrow the search with `path` to a subtree and `glob` to a filename pattern. Matching is case-insensitive unless you set `case_sensitive`.\n\nFinding nothing is a 200 with an empty `matches`, not a 404. Results are capped at `max_results` (200 by default, 1000 at most) and the output at 1 MB, and `truncated` tells you when either cap bit, so treat a `true` there as \"narrow the search\" rather than \"no more matches\".\n\nBase44 keeps its own protected trees out of the results, so a pattern that exists only there returns nothing.\n\nThis endpoint is limited to 120 requests per minute per app, shared with the other sandbox-bridge endpoints that only read.\n\n<Note>The sandbox bridge needs a Builder plan or higher on the app's workspace, and answers 402 below that. Workspace API keys are not authorized and are rejected with a 403, and it is unavailable for agent apps. A personal API key works as-is. An OAuth access token needs the `apps:read` scope; the read endpoints don't require `sandbox:write`.</Note>\n\n<Tip>Every error response carries a stable `extra_data.code` alongside the human-readable `message`. Branch on the code rather than on the message text or the status.</Tip>","operationId":"grep_endpoint_api_apps__app_id__sandbox_bridge_grep_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose sandbox to operate on.","title":"App Id"},"description":"ID of the app whose sandbox to operate on.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"default":{},"title":"SearchAppFiles","properties":{"branch_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional Base44 branch ID. Omit to operate on main.","title":"Branch Id"},"pattern":{"description":"Search pattern.","title":"Pattern","type":"string"},"path":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Subtree to search, relative to the app root. Default: whole app.","title":"Path"},"is_regex":{"default":true,"description":"Treat the pattern as a regex (default) or a literal string.","title":"Is Regex","type":"boolean"},"case_sensitive":{"default":false,"description":"Case-sensitive match. Default false.","title":"Case Sensitive","type":"boolean"},"glob":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional file glob filter, e.g. \"*.tsx\".","title":"Glob"},"max_results":{"default":200,"description":"Maximum number of match lines to return.","maximum":1000,"minimum":1,"title":"Max Results","type":"integer"}},"required":["pattern"]},"example":{"pattern":"useState","path":"src","glob":"*.jsx","max_results":50}}},"required":true},"responses":{"200":{"description":"The matching lines, empty when nothing matched.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrepResult"}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The app's workspace plan doesn't include the sandbox bridge."},"403":{"description":"You don't have access to this app, the app is blocked, your OAuth token is missing the scope this endpoint needs, or you used a workspace API key."},"404":{"description":"App not found, or `path` doesn't exist."},"429":{"description":"Rate limit exceeded (120 requests per minute)."},"400":{"description":"`path` points outside the app, or at a protected path."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/sandbox-bridge/list_directory":{"post":{"summary":"List app files","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 files and directories in the app's sandbox.\n\nEvery sandbox-bridge endpoint runs against the app's live sandbox, the same filesystem the AI edits, so a change here is visible in the app editor immediately.\n\nDefaults to the app root, one level deep. Set `path` to list a subtree, and `recursive: true` to go deeper, up to `max_depth` (3 by default, 10 at most). Dotfiles are left out unless you set `include_hidden`.\n\nDirectory entries carry no `size`; files do. Listings are capped at 1000 entries with `truncated` reporting it, and Base44's own protected trees never appear. Dependency and build directories such as `node_modules`, `dist` and `build` are pruned, so the listing stays the app's own source.\n\nA `path` that exists but isn't a directory answers 400, and one that doesn't exist answers 404.\n\nThis endpoint is limited to 120 requests per minute per app, shared with the other sandbox-bridge endpoints that only read.\n\n<Note>The sandbox bridge needs a Builder plan or higher on the app's workspace, and answers 402 below that. Workspace API keys are not authorized and are rejected with a 403, and it is unavailable for agent apps. A personal API key works as-is. An OAuth access token needs the `apps:read` scope; the read endpoints don't require `sandbox:write`.</Note>\n\n<Tip>Every error response carries a stable `extra_data.code` alongside the human-readable `message`. Branch on the code rather than on the message text or the status.</Tip>","operationId":"list_directory_endpoint_api_apps__app_id__sandbox_bridge_list_directory_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose sandbox to operate on.","title":"App Id"},"description":"ID of the app whose sandbox to operate on.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"default":{},"title":"ListAppFiles","properties":{"branch_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional Base44 branch ID. Omit to operate on main.","title":"Branch Id"},"path":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Directory relative to the app root. Default: app root.","title":"Path"},"recursive":{"default":false,"description":"List nested entries.","title":"Recursive","type":"boolean"},"max_depth":{"default":3,"description":"Max depth when recursive.","maximum":10,"minimum":1,"title":"Max Depth","type":"integer"},"include_hidden":{"default":false,"description":"Include dotfiles.","title":"Include Hidden","type":"boolean"}},"required":[]},"example":{"path":"src","recursive":true,"max_depth":2}}},"required":true},"responses":{"200":{"description":"The listing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListDirectoryResult"}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The app's workspace plan doesn't include the sandbox bridge."},"403":{"description":"You don't have access to this app, the app is blocked, your OAuth token is missing the scope this endpoint needs, or you used a workspace API key."},"404":{"description":"App not found, or `path` doesn't exist."},"429":{"description":"Rate limit exceeded (120 requests per minute)."},"400":{"description":"`path` isn't a directory, points outside the app, or at a protected path."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/sandbox-bridge/create_checkpoint":{"post":{"summary":"Create a sandbox 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 checkpoint you can return to.\n\nEvery sandbox-bridge endpoint runs against the app's live sandbox, the same filesystem the AI edits, so a change here is visible in the app editor immediately.\n\nThis is what makes sandbox work restorable. Writes through this API are committed to git but not checkpointed, and only checkpoints appear in the app editor's version history: a Restore or Revert there rolls the app back to the last checkpoint and discards everything written after it. Take a checkpoint when you finish a unit of work and always before you stop, and one before a risky batch of edits so a bad change is one restore away. Pending writes are flushed first, so the checkpoint captures the sandbox as it stands.\n\nRestore it with [Restore checkpoint](/api-reference/restore-checkpoint), and list what an app has with [List checkpoints](/api-reference/list-checkpoints). Checkpoints created here show up alongside the ones the AI takes.\n\nThis endpoint is limited to 60 requests per minute per app, shared with the other sandbox-bridge endpoints that change files.\n\n<Note>The sandbox bridge needs a Builder plan or higher on the app's workspace, and answers 402 below that. Workspace API keys are not authorized and are rejected with a 403, and it is unavailable for agent apps. A personal API key works as-is. An OAuth access token needs the `sandbox:write` scope.</Note>\n\n<Tip>Every error response carries a stable `extra_data.code` alongside the human-readable `message`. Branch on the code rather than on the message text or the status.</Tip>","operationId":"create_checkpoint_endpoint_api_apps__app_id__sandbox_bridge_create_checkpoint_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose sandbox to operate on.","title":"App Id"},"description":"ID of the app whose sandbox to operate on.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"default":{},"title":"CreateSandboxCheckpoint","properties":{"branch_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional Base44 branch ID. Omit to operate on main.","title":"Branch Id"},"name":{"anyOf":[{"maxLength":200,"type":"string"},{"type":"null"}],"description":"Optional message/title for the checkpoint. Defaults to an auto-generated title.","title":"Name"}},"required":[]},"example":{"name":"Before refactoring the dashboard"}}},"required":true},"responses":{"200":{"description":"The checkpoint that was created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCheckpointResult"}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The app's workspace plan doesn't include the sandbox bridge."},"403":{"description":"You don't have access to this app, the app is blocked, your OAuth token is missing the scope this endpoint needs, or you used a workspace API key."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded (60 requests per minute)."},"409":{"description":"The app is on a branch that can't be written to, or an earlier change is still being committed."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/code/file-list":{"get":{"summary":"List app code files","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 path of every file in the app's source, the list the app editor shows in its file tree.\n\nFiles the app's `.gitignore` excludes, dependency and build folders, most dotfiles, and binary or media files are left out. Read a file's text with [Get an app code file](/api-reference/get-an-app-code-file).\n\nReads come from the app's live sandbox, the same files the app editor shows, including changes that haven't been published yet. If no sandbox is running, the call starts one, so the first call after a quiet period takes noticeably longer than later ones.\n\nInternally this route can follow a feature branch, but the parameter that selects one isn't part of the public API, so reads come from the app's main line.\n\nThis is limited to 60 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key, including a read-only one, from anyone with access to the app, viewers included. Workspace API keys are not authorized for it.</Note>","operationId":"file_list_api_apps__app_id__code_file_list_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose code to read.","title":"App Id"},"description":"ID of the app whose code to read.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The path of every file in the app's source.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileList"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key."},"404":{"description":"App not found."},"429":{"description":"Too many requests for this app in the last minute from personal API keys in your workspace."}}}},"/api/apps/{app_id}/code/content":{"get":{"summary":"Get an app code file","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 text of one file in the app's source.\n\nGet paths from [List app code files](/api-reference/list-app-code-files). A file that list leaves out, such as `package-lock.json`, can still be read by naming it.\n\nOnly UTF-8 text files up to 5 MB can be read. A path outside the app is refused, and so are the app's `.git` folder and `.agents/.env`, which Base44 manages and which can hold credentials.\n\nReads come from the app's live sandbox, the same files the app editor shows, including changes that haven't been published yet. If no sandbox is running, the call starts one, so the first call after a quiet period takes noticeably longer than later ones.\n\nInternally this route can follow a feature branch, but the parameter that selects one isn't part of the public API, so reads come from the app's main line.\n\nThis is limited to 300 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key, including a read-only one, from anyone with access to the app, viewers included. Workspace API keys are not authorized for it.</Note>","operationId":"content_api_apps__app_id__code_content_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose code to read.","title":"App Id"},"description":"ID of the app whose code to read.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"path","in":"query","required":true,"schema":{"type":"string","description":"Path of the file, relative to the app root.","title":"Path"},"description":"Path of the file, relative to the app root.","example":"src/pages/Home.jsx"}],"responses":{"200":{"description":"The file's text.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileContent"}}}},"400":{"description":"`path` is empty, points outside the app, or is the app root."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, you used a workspace API key, or `path` is in the app's `.git` folder or is `.agents/.env`."},"404":{"description":"App not found, or there's no file at `path`."},"413":{"description":"The file is larger than 5 MB."},"415":{"description":"The file isn't UTF-8 text, or `path` is a folder."},"422":{"description":"`path` is missing."},"429":{"description":"Too many requests for this app in the last minute from personal API keys in your workspace."}}}},"/api/apps/{app_id}/code/content/batch":{"post":{"summary":"Get app code files","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 text of up to 32 files in the app's source in one call.\n\nGet paths from [List app code files](/api-reference/list-app-code-files). `files` is keyed by each path as Base44 normalized it, so `./src/App.jsx` comes back as `src/App.jsx`, and a path you send twice comes back once. Each file holds either its `content` or an `error`, so one unreadable file doesn't fail the call. Only UTF-8 text files up to 5 MB can be read.\n\n`max_bytes` caps the total size of the files returned. Files are read in the order you send them, and a file that would take the total over `max_bytes` comes back with the error `batch_limit_exceeded` and no content, while later files that still fit are read. Read the ones left out in another call. The other errors are `file_not_found`, `file_too_large` for a file over 5 MB, `not_text` for a folder or a file that isn't UTF-8 text, `file_forbidden`, `invalid_path` and `file_read_failed`.\n\nA path outside the app, the app root, the app's `.git` folder and `.agents/.env` fail the whole call rather than a single file.\n\nReads come from the app's live sandbox, the same files the app editor shows, including changes that haven't been published yet. If no sandbox is running, the call starts one, so the first call after a quiet period takes noticeably longer than later ones.\n\nInternally this route can follow a feature branch, but the parameter that selects one isn't part of the public API, so reads come from the app's main line.\n\nThis is limited to 120 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused even though this endpoint only reads, and workspace API keys are not accepted.</Note>","operationId":"batch_api_apps__app_id__code_content_batch_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose code to read.","title":"App Id"},"description":"ID of the app whose code to read.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadBatch"}}}},"responses":{"200":{"description":"Each file's text, or why it couldn't be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchResult"}}}},"400":{"description":"A path is empty, points outside the app, or is the app root."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, your API key is read-only, or you used a workspace API key, or a path is in the app's `.git` folder or is `.agents/.env`."},"404":{"description":"App not found."},"422":{"description":"`paths` is missing, empty, or holds more than 32 paths, or `max_bytes` is outside 1 to 5242880."},"429":{"description":"Rate limit exceeded. The base limit is 120 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/code/search":{"post":{"summary":"Search app code","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nFinds the lines in the app's source files that contain some text, the way the app editor's search does.\n\nThe text is matched literally, not as a pattern, and the match ignores case unless you set `case_sensitive`. Each result lists the matching lines of one file with their line numbers and up to 300 characters of the line around the match.\n\nSearch covers the files [List app code files](/api-reference/list-app-code-files) returns. Files over 5 MB and files that aren't text are skipped and counted in `skipped_files`. A search stops after 500 matching lines or 10 seconds, and `truncated` is then `true`, so narrow the text if you need every match.\n\nReads come from the app's live sandbox, the same files the app editor shows, including changes that haven't been published yet. If no sandbox is running, the call starts one, so the first call after a quiet period takes noticeably longer than later ones.\n\nInternally this route can follow a feature branch, but the parameter that selects one isn't part of the public API, so reads come from the app's main line.\n\nThis is limited to 60 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused even though this endpoint only reads, and workspace API keys are not accepted.</Note>","operationId":"search_api_apps__app_id__code_search_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose code to read.","title":"App Id"},"description":"ID of the app whose code to read.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchFiles"}}}},"responses":{"200":{"description":"The matching lines, grouped by file.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResults"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"422":{"description":"`query` is missing, shorter than 2 or longer than 200 characters, only whitespace, or spans more than one line."},"429":{"description":"Rate limit exceeded. The base limit is 60 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/openapi-spec":{"get":{"summary":"Get app API reference","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 reference for the app's own API, the one its code and other systems use to work with its data. It covers:\n- Each entity's endpoints to list, read, create, update, and delete records\n- The app's users\n- Its backend functions\n- Its agents\n\nThe reference is built from the app as it is now, so it can include entities and functions that haven't been published yet. Server addresses use the app's `base44.app` address, even when it also has a custom domain.\n\nThis is limited to 30 requests a minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"get_openapi_spec_api_apps__app_id__openapi_spec_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":"The app's API reference, as an OpenAPI 3.0.3 document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppOpenApiSpec"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"429":{"description":"Too many API reference requests for this app from you in the last minute."}}}},"/api/apps/{app_id}/testing-agent/flows":{"post":{"summary":"Create test","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 test to the app. A test is a goal written in plain language that the testing agent tries to accomplish in a real browser when you [run it](/api-reference/run-test).\n\nSet `role` to run the test as a user with that role. Leave it out and Base44 picks a role named in the test's name or goal, such as \"as an admin\", or runs as a regular user when neither names one.\n\nAn app can have up to 250 tests. Creating a test is free. Running it costs credits.\n\nThis is limited to 300 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. Read-only keys and workspace API keys are refused.</Note>","operationId":"create_flow_api_apps__app_id__testing_agent_flows_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/CreateFlowRequest"}}}},"responses":{"200":{"description":"The created test.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowResponse"}}}},"400":{"description":"`name` or `goal` is only whitespace, or the app already has 250 tests."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List tests","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 test in the app, newest first. An app has at most 250 tests, so the list is never paged.\n\nThis is limited to 6000 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with access to the app. Read-only keys are refused even though this endpoint only reads, and workspace API keys are not accepted.</Note>","operationId":"list_flows_api_apps__app_id__testing_agent_flows_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":"The app's tests.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FlowResponse"},"title":"Tests"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/testing-agent/flows/{flow_id}":{"get":{"summary":"Get test","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 of the app's tests.\n\nThis is limited to 6000 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with access to the app. Read-only keys are refused even though this endpoint only reads, and workspace API keys are not accepted.</Note>","operationId":"get_flow_api_apps__app_id__testing_agent_flows__flow_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"},{"name":"flow_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the test.","title":"Flow Id"},"description":"ID of the test.","example":"68a1c2e4f0b3d9001a7e5c21"}],"responses":{"200":{"description":"The test.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App or test not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}},"patch":{"summary":"Update test","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 test's name, goal or role. Send only the fields you want to change. Send `role` as `null` to clear it, so Base44 picks a role from the test's name or goal again.\n\nEach edit raises the test's `revision` by 1. You can't edit a test while it has a run going, so [cancel the run](/api-reference/cancel-test-run) or wait for it to end first.\n\nThis is limited to 300 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. Read-only keys and workspace API keys are refused.</Note>","operationId":"update_flow_api_apps__app_id__testing_agent_flows__flow_id__patch","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":"flow_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the test to update.","title":"Flow Id"},"description":"ID of the test to update.","example":"68a1c2e4f0b3d9001a7e5c21"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFlowRequest"}}}},"responses":{"200":{"description":"The updated test.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowResponse"}}}},"400":{"description":"You sent no fields, or `name` or `goal` is only whitespace."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App or test not found."},"409":{"description":"The test has a run going, or your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete test","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 test. This can't be undone. The test's past runs and their reports stay, and [List test runs](/api-reference/list-test-runs) still returns them.\n\nIf the test has a run going, the run is cancelled first and its browser session closed.\n\nThis is limited to 300 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. Read-only keys and workspace API keys are refused.</Note>","operationId":"delete_flow_api_apps__app_id__testing_agent_flows__flow_id__delete","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":"flow_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the test to delete.","title":"Flow Id"},"description":"ID of the test to delete.","example":"68a1c2e4f0b3d9001a7e5c21"}],"responses":{"200":{"description":"The test is deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestDeleted"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App or test not found, including a test you already deleted."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/testing-agent/refine":{"post":{"summary":"Refine test description","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 rough description into a test name and a goal the testing agent can follow. Nothing is saved. Pass the result to [Create test](/api-reference/create-test) to add it.\n\nThis makes an AI call and usually answers within a few seconds. It's billed by the length of `description`, so it usually costs a fraction of a credit. The call is refused when the workspace is out of credits.\n\nThis is limited to 20 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. Read-only keys and workspace API keys are refused.</Note>","operationId":"refine_flow_api_apps__app_id__testing_agent_refine_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/RefineFlowRequest"}}}},"responses":{"200":{"description":"The suggested name and goal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefinedTestDescription"}}}},"400":{"description":"The workspace is out of credits."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/testing-agent/flows/generate-from-app-context":{"post":{"summary":"Generate tests from app","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nHas Base44 read the app's code and builder conversation, write up to 5 tests for its main features, and save them. The response lists the saved tests. Start them with [Run test](/api-reference/run-test).\n\nThis makes AI calls and costs credits for the tests it saves. It runs while you wait, usually for 30 seconds to a minute. The call is refused when the workspace is out of credits.\n\nThe app needs room for 5 more tests, so it can have at most 245 when you call this. An app with no pages or entities yet gets no tests and no charge, with `no_changes` set to `true`.\n\nEach call adds new tests, even when similar ones exist. To get ideas that avoid the tests you have, use [Suggest new tests](/api-reference/suggest-new-tests). This call also saves a generation checkpoint, as [Save test generation checkpoint](/api-reference/save-test-generation-checkpoint) does.\n\nThis is limited to 20 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. Read-only keys and workspace API keys are refused.</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":"generate_from_app_context_api_apps__app_id__testing_agent_flows_generate_from_app_context_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":"The tests Base44 saved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeneratedTests"}}}},"400":{"description":"The app has more than 245 tests, or the workspace is out of credits."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/testing-agent/flows/suggest-new-tests":{"post":{"summary":"Suggest new tests","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns up to 5 test ideas that the app's existing tests don't cover yet. Nothing is saved. Add the ones you want with [Create test](/api-reference/create-test), then call [Save test generation checkpoint](/api-reference/save-test-generation-checkpoint).\n\nIf the app changed since the last checkpoint, the ideas focus on what changed. Otherwise, or when the app has no checkpoint yet, they cover gaps across the whole app.\n\nThis makes AI calls and costs credits for the ideas it returns. It runs while you wait, usually for 30 seconds to a minute and sometimes longer. The call is refused when the workspace is out of credits, or when the app has more than 245 tests.\n\nThis is limited to 20 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. Read-only keys and workspace API keys are refused.</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":"suggest_new_tests_api_apps__app_id__testing_agent_flows_suggest_new_tests_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":"The suggested tests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestSuggestions"}}}},"400":{"description":"The app has more than 245 tests, or the workspace is out of credits."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/testing-agent/flows/save-generation-checkpoint":{"post":{"summary":"Save test generation checkpoint","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRecords the app's current state, so the next [Suggest new tests](/api-reference/suggest-new-tests) focuses on what changes after this point. Call it once you've added the suggestions you want. [Generate tests from app](/api-reference/generate-tests-from-app) saves one on its own.\n\nThe state is the app's latest code version and the length of its builder conversation. Each call moves the checkpoint forward. It's free.\n\nThis is limited to 300 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. Read-only keys and workspace API keys are refused.</Note>","operationId":"save_generation_checkpoint_api_apps__app_id__testing_agent_flows_save_generation_checkpoint_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":"The checkpoint is saved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestGenerationCheckpointSaved"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/testing-agent/executions":{"post":{"summary":"Run test","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 run of a test. The testing agent opens `url` in a cloud browser, signs in as a temporary test user, and works toward the test's goal. A report follows once the browser session ends.\n\nLeave `url` out and the run starts on the app's current preview, the same one [Get preview URL](/api-reference/get-preview-url) returns. If the preview isn't running, this call starts it first, so it can take noticeably longer. The run works against the app's test data, not its live data.\n\nTo start on a different page, send `url` yourself. It must be on `base44.app` or `base44.com`, and a preview page also needs a fresh `_preview_token` from Get preview URL in its query, because each token works once and expires after 5 minutes.\n\nThe call returns once the run is queued. A run that repeats an earlier one can come back already finished. Otherwise poll [Get test run](/api-reference/get-test-run) until `status` is no longer `pending`, `running` or `analyzing`. Then read the verdict with [Get test run report](/api-reference/get-test-run-report).\n\nA run costs credits, and `credits_charged` on the finished run shows how many. The call is refused when the workspace is out of credits. A run that runs out of credits partway through stops with `status` set to `paused` and `failure_reason` set to `out_of_credits`.\n\nA test runs one at a time. Starting it again while a run is still going returns a `409`.\n\nThis is limited to 300 requests every 600 seconds per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. Read-only keys and workspace API keys are refused.</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_flow_api_apps__app_id__testing_agent_executions_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":{"type":"object","title":"RunTest","required":["flow_id"],"properties":{"flow_id":{"type":"string","description":"ID of the test to run.","example":"68a1c2e4f0b3d9001a7e5c21"},"url":{"type":"string","maxLength":2000,"description":"Page the run starts on. Leave it out to start on the app's current preview.","example":"https://preview-6820f3a4e7b91d003c45a1f2.base44.app/?_preview_token=FH-j7wHS7IR_fC1tUh4wCd_fNV1XGC479cuqNSYi9mA"}}}}}},"responses":{"200":{"description":"The queued run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestRun"}}}},"400":{"description":"`url`, or the app's preview when you leave `url` out, isn't on a Base44 host, or the workspace is out of credits."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App or test not found."},"409":{"description":"The test already has a run going, the app's preview couldn't start because its code doesn't build, or your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List test runs","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 100 most recent test runs, newest first. Set `flow_id` to get only one test's runs. Older runs can't be listed.\n\nThis is limited to 6000 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with access to the app. Read-only keys are refused even though this endpoint only reads, and workspace API keys are not accepted.</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_executions_api_apps__app_id__testing_agent_executions_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":"flow_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only return runs of this test.","title":"Flow Id"},"description":"Only return runs of this test.","example":"68a1c2e4f0b3d9001a7e5c21"}],"responses":{"200":{"description":"The runs, newest first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TestRun"},"title":"TestRuns"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/testing-agent/executions/latest-by-flow":{"get":{"summary":"Get latest run per test","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns each test's most recent run, with its report, in one call. The response is an object keyed by test ID. A test that has never run isn't in it, and a deleted test's last run still is.\n\nOnly the app's 1,000 most recent runs are considered, so a test whose last run is older than that is left out. The object isn't paged.\n\nThis is limited to 6000 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with access to the app. Read-only keys are refused even though this endpoint only reads, and workspace API keys are not accepted.</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_latest_executions_by_flow_api_apps__app_id__testing_agent_executions_latest_by_flow_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":"Each test's most recent run, keyed by test ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LatestTestRuns"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/testing-agent/executions/{execution_id}":{"get":{"summary":"Get test run","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 test run. Poll it after [Run test](/api-reference/run-test) to follow the run: it has ended once `status` is no longer `pending`, `running` or `analyzing`.\n\nThis is limited to 6000 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with access to the app. Read-only keys are refused even though this endpoint only reads, and workspace API keys are not accepted.</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_execution_api_apps__app_id__testing_agent_executions__execution_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"},{"name":"execution_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the run.","title":"Execution Id"},"description":"ID of the run.","example":"68a1c9b7f0b3d9001a7e5d04"}],"responses":{"200":{"description":"The run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestRun"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App or run not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/testing-agent/executions/{execution_id}/cancel":{"post":{"summary":"Cancel test run","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 test run that's still going and closes its browser session. The run ends with `status` set to `cancelled`.\n\nCancelling a run that already ended changes nothing and returns `already_terminal` set to `true`, so retrying is safe.\n\nThis is limited to 300 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. Read-only keys and workspace API keys are refused.</Note>","operationId":"cancel_execution_api_apps__app_id__testing_agent_executions__execution_id__cancel_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"},{"name":"execution_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the run to cancel.","title":"Execution Id"},"description":"ID of the run to cancel.","example":"68a1c9b7f0b3d9001a7e5d04"}],"responses":{"200":{"description":"The run is cancelled or had already ended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelTestRunResult"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App or run not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/testing-agent/executions/{execution_id}/pause":{"post":{"summary":"Pause test run","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 test run that's `pending` or `running` and closes its browser session. The run ends with `status` set to `paused` and no `failure_reason`, which is how you tell it apart from a run paused for running out of credits. It gets no report. A paused run can't be resumed, so [run the test](/api-reference/run-test) again to start over.\n\nA run that's already `analyzing` isn't paused, because its browser session is over. The call returns `already_terminal` set to `true` and the run goes on to get its report. A run that already ended also returns `already_terminal` set to `true`, so retrying is safe.\n\nThis is limited to 300 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. Read-only keys and workspace API keys are refused.</Note>","operationId":"pause_execution_api_apps__app_id__testing_agent_executions__execution_id__pause_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"},{"name":"execution_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the run to pause.","title":"Execution Id"},"description":"ID of the run to pause.","example":"68a1c9b7f0b3d9001a7e5d04"}],"responses":{"200":{"description":"The run is paused, or nothing changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PauseTestRunResult"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App or run not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/testing-agent/analysis/{execution_id}":{"get":{"summary":"Get test run report","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 report for a test run: the verdict, a summary, and the problems the testing agent found.\n\nWait until the run's `analysis_status` in [Get test run](/api-reference/get-test-run) is `completed`. Until the report is written this returns a `404`, and a run that stopped before analysis, such as most cancelled runs, never gets one.\n\nProblems caused by Base44's own preview or infrastructure are left out of `issues`, because they aren't problems in your app.\n\nThis is limited to 6000 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with access to the app. Read-only keys are refused even though this endpoint only reads, and workspace API keys are not accepted.</Note>","operationId":"get_analysis_api_apps__app_id__testing_agent_analysis__execution_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"},{"name":"execution_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the run.","title":"Execution Id"},"description":"ID of the run.","example":"68a1c9b7f0b3d9001a7e5d04"}],"responses":{"200":{"description":"The run's report.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestRunReport"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App not found, or the run has no report yet."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/testing-agent/analysis/{execution_id}/reanalyze":{"post":{"summary":"Reanalyze test run","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 new report for a finished test run from the steps the run already recorded. The browser doesn't run again. The new report replaces the old one, and the run's `goal_accomplished` follows the new verdict.\n\nIt also finishes a run stuck in `analyzing` whose steps were saved, and that run then ends normally.\n\nThis makes an AI call and costs credits, which are recorded against the user who started the run. It runs while you wait, usually for under a minute, and returns the new report. If it fails, the old report stays. The call is refused when the workspace is out of credits.\n\nThis is limited to 20 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. Read-only keys and workspace API keys are refused.</Note>","operationId":"reanalyze_execution_api_apps__app_id__testing_agent_analysis__execution_id__reanalyze_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"},{"name":"execution_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the run to reanalyze.","title":"Execution Id"},"description":"ID of the run to reanalyze.","example":"68a1c9b7f0b3d9001a7e5d04"}],"responses":{"200":{"description":"The new report.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestRunReport"}}}},"400":{"description":"The run recorded no steps, or the workspace is out of credits."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App or run not found."},"409":{"description":"The run is still `pending`, `running` or `analyzing`, another reanalysis of it is already in progress, or your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/testing-agent/analysis/{execution_id}/issues/delete":{"post":{"summary":"Delete test run issue","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nDismisses an issue from a run's report as a false alarm. It's removed from the report, and future runs of the same test leave it out too, along with issues that say the same thing in other words. Use [Resolve test run issue](/api-reference/resolve-test-run-issue) instead when you fixed the problem and want the next run to check again.\n\nThe issue is matched by `title` against the run's current report. Every issue with that title is removed. There's no API to bring a dismissed issue back. Dismissing it again returns the report unchanged, so retrying is safe.\n\nThis is limited to 300 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. Read-only keys and workspace API keys are refused.</Note>","operationId":"delete_analysis_issue_api_apps__app_id__testing_agent_analysis__execution_id__issues_delete_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"},{"name":"execution_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the run whose report to change.","title":"Execution Id"},"description":"ID of the run whose report to change.","example":"68a1c9b7f0b3d9001a7e5d04"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteIssueRequest"}}}},"responses":{"200":{"description":"The report, without the issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestRunReport"}}}},"400":{"description":"`title` is empty once punctuation and spaces are removed."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App, run, or report not found, or the report has no issue with that title."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/testing-agent/analysis/{execution_id}/issues/resolve":{"post":{"summary":"Resolve test run issue","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nMarks an issue in a run's report as fixed and removes it from the report. Unlike [Delete test run issue](/api-reference/delete-test-run-issue), it doesn't hide the issue from future runs, so the next run of the test reports it again if the problem is still there.\n\nThe issue is matched by `title` against the run's current report. Every issue with that title is removed. Resolving it again returns the report unchanged, so retrying is safe.\n\nThis is limited to 300 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. Read-only keys and workspace API keys are refused.</Note>","operationId":"resolve_analysis_issue_api_apps__app_id__testing_agent_analysis__execution_id__issues_resolve_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"},{"name":"execution_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the run whose report to change.","title":"Execution Id"},"description":"ID of the run whose report to change.","example":"68a1c9b7f0b3d9001a7e5d04"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolveIssueRequest"}}}},"responses":{"200":{"description":"The report, without the issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestRunReport"}}}},"400":{"description":"`title` is empty once punctuation and spaces are removed."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App, run, or report not found, or the report has no issue with that title."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/testing-agent/data/seed":{"post":{"summary":"Seed test data","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nFills the app's test data with sample records for the testing agent to work with. A run on the app's preview uses test data, and [Run test](/api-reference/run-test) seeds it when such a run starts, so call this only to refresh the test data yourself.\n\nFor each entity other than `User`:\n\n- When the entity has live records, up to 5 copies of them are written to test data, including any personal details they hold. Live data is only read, never changed.\n- Otherwise, for up to 5 such entities, AI makes up to 3 records each from the entity's schema. This is skipped for an entity whose test data has records someone added by hand, and for an entity with no schema fields.\n\nNew records replace the ones an earlier seed wrote. Test records someone added by hand are kept.\n\nCopying is free. Making up records costs credits, and the call is refused when it needs to make some up and the workspace is out of credits, though records already copied are kept. It runs while you wait, usually for 20 to 45 seconds and up to a few minutes on an app with many entities, so allow a long timeout. Seeds of one app run one at a time. A call made while another is running waits for it and then usually seeds again, replacing what the first one wrote and charging again for any AI-made records. If the wait runs too long, it gives up and reports no records written.\n\nThis is limited to 30 requests per minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. Read-only keys and workspace API keys are refused.</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":"seed_data_api_apps__app_id__testing_agent_data_seed_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":"What was written into the app's test data.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeededTestData"}}}},"400":{"description":"Some entities need AI-made records and the workspace is out of credits."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or your API key is read-only or a workspace API key."},"404":{"description":"App not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/sso/settings":{"get":{"summary":"Get app SSO settings","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 own SSO provider and its settings. This is the provider set up for this app, not the workspace's SSO.\n\nThe client secret is never returned. `client_secret` reads as a fixed mask when one is stored. Each provider only returns the settings it uses, so the rest read as empty strings.\n\nChange them with [Update app SSO settings](/api-reference/update-app-sso-settings).\n\nThis is limited to 60 requests a minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"get_app_sso_settings_api_apps__app_id__sso_settings_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":"The app's SSO provider settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSSOSettingsResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"429":{"description":"Too many requests for this app's SSO settings from you in the last minute."}}},"put":{"summary":"Update app SSO settings","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSets up or changes the app's own SSO provider, and turns SSO sign-in on for the app. The app's other login methods stay as they are. Turn those off with [Update app](/api-reference/update-app).\n\nThe credentials and URLs you send take effect on the published app right away, including when you switch providers, so a switch can change or break live sign-ins before you deploy. The provider `name` and SSO being turned on reach the published app once you [deploy the app](/api-reference/deploy-an-app).\n\nSend only the fields you want to change. A field you leave out keeps its stored value, and an empty string clears it. Sending the masked `client_secret` from [Get app SSO settings](/api-reference/get-app-sso-settings) keeps the stored secret.\n\nChanging `name` to a different provider deletes everything stored for the previous one, so send the new provider's settings in the same request.\n\nAfter the save, the app needs a `client_id`, a `client_secret`, and either a `discovery_url` or both an `auth_endpoint` and a `token_endpoint`. A request that would leave any of these missing is rejected and nothing is saved.\n\nThe `discovery_url` is fetched before saving. If it can't serve a sign-in, the request is rejected. If the check fails in a way that may be temporary, the settings are saved and the response carries a `warning`.\n\nTurning SSO on for an app that doesn't use it yet needs a plan that includes SSO for apps. An app that already uses SSO can keep changing its settings.\n\nThe settings are stored as the app's secrets whose names start with `sso_`, and the app's backend functions, if it has any, redeploy to pick them up.\n\nThis is limited to 20 requests a minute per caller for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"update_app_sso_settings_api_apps__app_id__sso_settings_put","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/AppSSOUpdateRequest"},"example":{"name":"okta","client_id":"0oa8f2k1xyzAbCdE5d7","client_secret":"kq3Vt1-9dPzLr0aYbN2x","okta_domain":"acme.okta.com","discovery_url":"https://acme.okta.com/.well-known/openid-configuration"}}}},"responses":{"200":{"description":"The settings were saved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSSOUpdateResponse"}}}},"400":{"description":"No provider `name` was sent and the app has none, the settings would be incomplete, a URL isn't an absolute public `http` or `https` URL, or the `discovery_url` can't serve a sign-in."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"You're turning SSO on, and the app's workspace plan doesn't include SSO for apps."},"403":{"description":"You don't have editor access to this app, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"422":{"description":"The body isn't a JSON object, or a field isn't a string."},"429":{"description":"Too many SSO settings updates for this app from you in the last minute."}}}},"/api/apps/{app_id}/embed-url":{"post":{"summary":"Create an embed sign-in token","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 single-use sign-in token for a provisioned app user and returns `embed_url`, the chosen surface's address with the token on it. Load it in an iframe and the app opens signed in as that user. `live_preview` loads only while the app's sandbox is running. A workspace API key needs the **Mint embed sign-in tokens** permission. Limited to 300 requests a minute per app. Higher plans get a higher limit.","operationId":"create_embed_token_api_apps__app_id__embed_url_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/EmbedTokenPayload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedTokenResponse"}}}},"400":{"description":"The app has no address for `target`: error.code app_not_deployed or app_has_no_slug."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The email belongs to the app's owner, an editor or a service account: error.code privileged_user."},"404":{"description":"The email isn't provisioned for this app: error.code unknown_user."},"429":{"description":"The app went over its per-minute limit for embed sign-in tokens."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"PersonalAccessTokenAuth":[]},{"WorkspaceApiKeyAuth":[]}]}},"/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. A saved version whose functions no longer compile uses error.code checkpoint_source_no_longer_compiles, with the failing functions and compiler output in error.details."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"App is blocked, or the caller can't publish it. Personal GitHub access refusals use error.code github_write_access_required."},"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}/undeploy":{"post":{"summary":"Unpublish app","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nTakes a published app offline.\n\nVisitors get an offline notice instead of the app. You can keep working on it in the editor, and its data, settings, and custom domains stay as they are. To put it back online, publish it again with [Deploy an app](/api-reference/deploy-an-app).\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</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":"undeploy_api_apps__app_id__undeploy_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to unpublish.","title":"App Id"},"description":"ID of the app to unpublish.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The app, now offline.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnpublishedApp"}}}},"400":{"description":"The app has never been published, it's already offline, or your workspace's publishing policy doesn't let you publish it."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or your API key is read-only."},"404":{"description":"App not found."}}}},"/api/apps/{app_id}/request-deploy":{"post":{"summary":"Request deploy approval","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 owners and admins of the app's workspace to publish the app for you, with the visibility you want. Use it when your workspace's publishing policy doesn't let you [deploy the app](/api-reference/deploy-an-app) yourself.\n\nEvery active owner, and every admin allowed to publish, except you, gets an in-app notification and an email naming the app, the visibility you asked for, and your `message`. Base44 also records the request as pending until the app is published. The call doesn't publish anything or check whether you could have published the app yourself.\n\nRepeating the request within 60 seconds notifies no one and returns `deduped` set to `true`. After that, the same request notifies the approvers again.\n\nThis is limited to 5 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"request_deploy_api_apps__app_id__request_deploy_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app you want published.","title":"App Id"},"description":"ID of the app you want published.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestDeployRequest"}}}},"responses":{"200":{"description":"The approvers were notified, or the request was a repeat.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeployApprovalRequested"}}}},"400":{"description":"This kind of app can't be published yet."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"409":{"description":"No one else in the app's workspace can approve the request, for example because you're its only owner."},"422":{"description":"`requested_visibility` is missing or isn't one of the four values, or `message` is longer than 1000 characters."},"429":{"description":"Rate limit exceeded. The base limit is 5 requests per minute per app. Wait the number of seconds in the `Retry-After` header before you retry."}}}},"/api/apps/{app_id}/deploy-dist":{"post":{"summary":"Deploy a prebuilt site","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nPublishes a site you built yourself. Upload the build output as a `.tar.gz` archive, and it's live on the app's published URL by the time the request returns.\n\nArchive the contents of the build folder rather than the folder itself, so that `index.html` sits at the root of the archive. The archive can be up to 50 MB, both as uploaded and once extracted, and can hold up to 1,000 files. Links, and files whose path is absolute or contains `..` or `:`, are left out without an error.\n\nThe archive replaces the whole published site. It doesn't change the app's code, backend functions or entities. An app that isn't published yet, or that was unpublished, is published by this. Sending an archive identical to one the app already uploaded publishes the stored copy without uploading it again.\n\nWith a personal API key you need permission to publish apps in the app's workspace. A workspace API key with the `apps:deploy` scope doesn't need it.\n\nThis is limited to 5 requests per minute for each workspace's API keys, counted across all of the workspace's apps, so every personal and workspace API key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app, or a workspace API key with the `apps:deploy` scope. A read-only key is refused.</Note>","operationId":"deploy_dist_api_apps__app_id__deploy_dist_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":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/DeployPrebuiltSite"}}}},"responses":{"200":{"description":"The deploy is live.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeployDistResponse"}}}},"400":{"description":"The file name doesn't end in `.tar.gz`, the archive is larger than 50 MB uploaded or extracted, holds more than 1,000 files, is empty or can't be read, or has no `index.html` at its root. Grow apps return `grow_publishing_unavailable` in the standard error envelope because they only create draft assets."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, you can't publish apps in its workspace, your API key is read-only, or your workspace API key lacks the `apps:deploy` scope or doesn't cover this app."},"404":{"description":"App not found."},"409":{"description":"With a workspace API key, the app left the key's workspace or the key was revoked during the request. Nothing was published."},"422":{"description":"`file` is missing."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/cmo/state":{"get":{"summary":"Get Marketing Agent 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 the Marketing Agent's plan for an app and the state of its latest run. That covers the setup step it's on, the channels, KPIs, and strategy it proposed, and the competitors it found.\n\nThe plan is shared by everyone who edits the app. An app that never used the Marketing Agent returns `status` of `idle` and `setup_stage` of `not_started`, rather than a 404.\n\nPoll this while a run is working. The agent's replies appear in the app's chat, which you read with [Read conversation messages](/api-reference/read-conversation-messages).\n\nThis is limited to 120 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance, across every Marketing Agent endpoint. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app and an editor role in its workspace. Read-only keys and workspace API keys are refused.</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_cmo_state_api_apps__app_id__cmo_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/MarketingAgentStateResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app or an editor role in its workspace, the app is a mobile app, Superagent is turned off for the workspace, or you used a read-only or workspace API key. Setup, chat, generation and enabling autonomy also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, reset and disabling autonomy remain available."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/cmo/kpi-values":{"get":{"summary":"Get Marketing Agent KPI values","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 each KPI in the Marketing Agent's plan measures right now, read from the app's analytics.\n\n`values` is in the same order as `kpis` in [Get Marketing Agent state](/api-reference/get-marketing-agent-state), so match the two by position rather than by name. A plan with no KPIs yet returns an empty list. Read `state` before `value`: a KPI that can't be measured has no `value` rather than a `0`.\n\nValues can be up to an hour old. They're read again when the KPIs change, and on every call while any KPI is `not_tracked` or `unavailable`, so a newly recorded event shows up on the next call.\n\nThis is limited to 120 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance, across every Marketing Agent endpoint. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app and an editor role in its workspace. Read-only keys and workspace API keys are refused.</Note>","operationId":"get_cmo_kpi_values_api_apps__app_id__cmo_kpi_values_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/KpiValuesResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app or an editor role in its workspace, the app is a mobile app, Superagent is turned off for the workspace, or you used a read-only or workspace API key. Setup, chat, generation and enabling autonomy also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, reset and disabling autonomy remain available."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/cmo/setup/start":{"post":{"summary":"Start Marketing Agent setup","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 Marketing Agent's setup for an app. The agent reads the app and drafts marketing channels and KPIs for you to review.\n\nThe first run in a workspace also creates the workspace's Marketing Agent, a Superagent that everyone in the workspace shares.\n\nThe work runs in the background after the call returns. Poll [Get Marketing Agent state](/api-reference/get-marketing-agent-state) until `status` is `ready` or `error`. If `competitor_scan_status` is `running`, keep polling until it is no longer `running`. Setup then waits at a `setup_stage` of `channels`. Approve each step with [Answer Marketing Agent setup step](/api-reference/answer-marketing-agent-setup-step), or ask for changes with [Send Marketing Agent message](/api-reference/send-marketing-agent-message).\n\nWhile the scan is running, or once setup is past it, the call returns the current state and starts nothing. After a failed scan it starts the scan again, which is a new charged run.\n\nTo retry safely, send an `Idempotency-Key` header. A retry with the same key within 24 hours returns the current state and starts nothing, even if the first scan failed.\n\nEach run is a Superagent turn, charged to the app's workspace in message credits based on the tokens it uses. A run can take several minutes and is stopped after 15. If the workspace is out of credits the run doesn't start, and `last_error` says so.\n\nThis is limited to 120 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance, across every Marketing Agent endpoint. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app and an editor role in its workspace. Read-only keys and workspace API keys are refused.</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_cmo_setup_api_apps__app_id__cmo_setup_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"},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":100},{"type":"null"}],"description":"Your key for this call. A retry with the same key within 24 hours returns the current state and starts nothing.","title":"Idempotency-Key"},"description":"Your key for this call. A retry with the same key within 24 hours returns the current state and starts nothing.","example":"weekly-plan-2026-09-29"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketingAgentStateResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app or an editor role in its workspace, the app is a mobile app, Superagent is turned off for the workspace, or you used a read-only or workspace API key. Setup, chat, generation and enabling autonomy also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, reset and disabling autonomy remain available."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded."},"422":{"description":"`Idempotency-Key` is longer than 100 characters."}}}},"/api/apps/{app_id}/cmo/setup/reset":{"post":{"summary":"Reset Marketing Agent setup","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nThrows away the app's marketing plan and puts the Marketing Agent back to before setup started. Start again with [Start Marketing Agent setup](/api-reference/start-marketing-agent-setup).\n\nThe plan is shared by everyone who edits the app, so this clears it for all of them, including their conversations with the agent about it. The channels, KPIs, strategy, budget, and competitors are all cleared, and `reset_epoch` goes up by one.\n\nReset never refuses, so you can use it when setup is stuck. A run that's working when you reset isn't stopped: it keeps running and using credits, but nothing it produces is saved. Calling it again resets the plan again.\n\nThis is limited to 120 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance, across every Marketing Agent endpoint. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app and an editor role in its workspace. Read-only keys and workspace API keys are refused.</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":"reset_cmo_setup_api_apps__app_id__cmo_setup_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/MarketingAgentStateResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app or an editor role in its workspace, the app is a mobile app, Superagent is turned off for the workspace, or you used a read-only or workspace API key. Setup, chat, generation and enabling autonomy also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, reset and disabling autonomy remain available."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/cmo/setup/answer":{"post":{"summary":"Answer Marketing Agent setup step","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nAnswers the setup step the Marketing Agent is waiting on with a fixed choice, without an agent turn.\n\nSend the state's `setup_stage` as `stage`, along with its `reset_epoch`. Each step takes its own answers:\n- `channels` takes `channels_ok`, which approves the channels and moves setup to `kpis`.\n- `kpis` takes `kpis_ok`, which approves the KPIs and moves setup to `budget`.\n- `budget` takes `budget` and `competitor_research`, together or one at a time. Once both are known, setup moves to `generating` and the agent writes the strategy. Send `budget_go` on its own to confirm a pair that's already stored.\n\nTo ask for a change instead, use [Send Marketing Agent message](/api-reference/send-marketing-agent-message).\n\nMost answers are free and take effect at once. The one that completes the budget step starts the strategy run. The work runs in the background after the call returns. Poll [Get Marketing Agent state](/api-reference/get-marketing-agent-state) until `status` is `ready` or `error`. If `competitor_scan_status` is `running`, keep polling until it is no longer `running`.\n\nEach run is a Superagent turn, charged to the app's workspace in message credits based on the tokens it uses. A run can take several minutes and is stopped after 15. If the workspace is out of credits the run doesn't start, and `last_error` says so.\n\nTo retry safely, send the same `message_id` or `Idempotency-Key` header again. A retry returns the current state and applies nothing, so it never starts a second strategy run.\n\nThis is limited to 120 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance, across every Marketing Agent endpoint. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app and an editor role in its workspace. Read-only keys and workspace API keys are refused.</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":"answer_cmo_setup_step_api_apps__app_id__cmo_setup_answer_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"},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":100},{"type":"null"}],"description":"Your key for this answer, as an alternative to `message_id`. A retry with the same key returns the current state and applies nothing. Ignored when the body carries `message_id`.","title":"Idempotency-Key"},"description":"Your key for this answer, as an alternative to `message_id`. A retry with the same key returns the current state and applies nothing. Ignored when the body carries `message_id`.","example":"weekly-plan-2026-09-29"},{"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/SetupAnswerPayload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketingAgentStateResponse"}}}},"400":{"description":"The answers don't match the questions `stage` asks."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app or an editor role in its workspace, the app is a mobile app, Superagent is turned off for the workspace, or you used a read-only or workspace API key. Setup, chat, generation and enabling autonomy also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, reset and disabling autonomy remain available."},"404":{"description":"App not found, or `stage` isn't `channels`, `kpis`, or `budget`."},"429":{"description":"Rate limit exceeded."},"409":{"description":"Setup isn't on `stage` any more, the plan was reset since `reset_epoch`, the agent is still working, `budget_go` was sent before both budget answers were stored, or `message_id` was already used for a different message."},"422":{"description":"The body is malformed, the `budget` answer has an empty `selected_label`, the `competitor_research` answer has no `value`, or `Idempotency-Key` is longer than 100 characters."}}}},"/api/apps/{app_id}/cmo/messages":{"post":{"summary":"Send Marketing Agent 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 Marketing Agent and starts the turn that answers it. During setup the message answers or revises the step the agent is on. After setup it's an ordinary conversation about the app's marketing.\n\nA message sent before setup has started opens it, the same way [Start Marketing Agent setup](/api-reference/start-marketing-agent-setup) does.\n\nThe work runs in the background after the call returns. Poll [Get Marketing Agent state](/api-reference/get-marketing-agent-state) until `status` is `ready` or `error`. If `competitor_scan_status` is `running`, keep polling until it is no longer `running`. The reply appears in the app's chat, which you read with [Read conversation messages](/api-reference/read-conversation-messages).\n\nThe agent takes one turn at a time, so a message sent while it's working is rejected. Send it again once `status` is `ready` or `error`. If writing the strategy failed partway, a message starts writing it again and is itself rejected until that finishes.\n\nSet `message_id` to a UUID of your own, or send an `Idempotency-Key` header instead. A retry that carries the same `message_id` or key returns the current state without starting or charging a second turn.\n\nEach run is a Superagent turn, charged to the app's workspace in message credits based on the tokens it uses. A run can take several minutes and is stopped after 15. If the workspace is out of credits the run doesn't start, and `last_error` says so.\n\nThis is limited to 120 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance, across every Marketing Agent endpoint. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app and an editor role in its workspace. Read-only keys and workspace API keys are refused.</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":"add_cmo_message_api_apps__app_id__cmo_messages_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"},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":100},{"type":"null"}],"description":"Your key for this message, as an alternative to `message_id`. A retry with the same key doesn't start a second turn. Ignored when the body carries `message_id`.","title":"Idempotency-Key"},"description":"Your key for this message, as an alternative to `message_id`. A retry with the same key doesn't start a second turn. Ignored when the body carries `message_id`.","example":"weekly-plan-2026-09-29"},{"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/SendMessagePayload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketingAgentStateResponse"}}}},"400":{"description":"`content` is empty."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app or an editor role in its workspace, the app is a mobile app, Superagent is turned off for the workspace, or you used a read-only or workspace API key. Setup, chat, generation and enabling autonomy also return `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads, reset and disabling autonomy remain available."},"404":{"description":"App not found."},"429":{"description":"Rate limit exceeded."},"409":{"description":"The agent is still working on an earlier turn, setup moved on while the message was sent, or `message_id` was already used for a different message."},"422":{"description":"The body is malformed, `content` is longer than 20,000 characters, `message_id` isn't a UUID, or `Idempotency-Key` is longer than 100 characters."}}}},"/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. New social content generation and edits also return 403 with code `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads and resetting the plan remain available.</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. New social content generation and edits also return 403 with code `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads and resetting the plan remain available.</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. New social content generation and edits also return 403 with code `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads and resetting the plan remain available.</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. New social content generation and edits also return 403 with code `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads and resetting the plan remain available.</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\nFor a generated calendar post, pass its `source_post_id` as `post_id` and its `id` as `scheduled_post_id`. This also supports replenished posts absent from the original plan. The response still contains the original plan; reload List scheduled posts to read the edited row. A 404 (`social_calendar_post_not_found`) means the row does not match this app and source post. A 409 (`social_calendar_post_not_editable` or `social_calendar_post_changed`) means the row cannot be edited or changed during generation; reload it before retrying.\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 **409**. Retrying is the right response, but check the post's current `content` first, because your edit is the version that survived. The same 409 also covers 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. New social content generation and edits also return 403 with code `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads and resetting the plan remain available.</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 requested plan or calendar post wasn't found, or the social content feature is not enabled for your account."},"409":{"description":"The calendar post cannot be edited, the post was edited while refining and the refinement was discarded, 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 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/teaser":{"post":{"summary":"Generate teaser posts","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nGenerates two social posts for an app, one for Instagram and one for LinkedIn, each with an image. They're written from the app alone, with no questions to answer first, and they're separate from the content plan.\n\nThe posts are generated once per app. Calling this again returns the existing posts rather than new ones, and a call while they're generating starts nothing. A failed generation can run again a day after it failed, and one that stops without finishing can run again after 4 minutes. Base44 can also regenerate the posts once after a change to their format.\n\nThe call returns as soon as generation starts, with a `status` of `generating`. Poll [Get social content state](/api-reference/get-social-content-state) and read `teaser` until its `status` is `ready` or `failed`. Generation takes up to 3 minutes. When Base44 has teaser generation turned off, the call returns a `status` of `null` and no posts.\n\nThis endpoint calls a language model and an image model, but it's free. It doesn't use any credits.\n\nThis is limited to 5 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance, separately from the other social content endpoints. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403. New social content generation and edits also return 403 with code `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads and resetting the plan remain available.</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":"generate_teaser_api_api_apps__app_id__virality_teaser_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/TeaserSummary"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or you used a read-only or workspace API key."},"404":{"description":"App not found, or the social content feature is not enabled for your account."},"429":{"description":"Rate limit exceeded (5 requests per minute)."}}}},"/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. New social content generation and edits also return 403 with code `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads and resetting the plan remain available.</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. New social content generation and edits also return 403 with code `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads and resetting the plan remain available.</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\nFor a generated calendar post, pass its `source_post_id` as `post_id` and its `id` as `scheduled_post_id`. The image is saved directly on that calendar row, including replenished posts absent from the plan. The returned plan remains the original plan; reload List scheduled posts for the updated row. A 404 (`social_calendar_post_not_found`) means the row does not match the app and source post. A 409 (`social_calendar_post_not_editable` or `social_calendar_post_changed`) means the row cannot be edited or changed during generation; reload it before retrying.\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. New social content generation and edits also return 403 with code `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads and resetting the plan remain available.</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 requested plan or calendar post wasn't found, or the social content feature is not enabled for your account."},"409":{"description":"A content plan is generating, or the calendar post cannot be edited or changed during generation."},"422":{"description":"The image provider's content policy refused this prompt (`error.code` is `image_generation_refused`), or the request body failed validation."},"429":{"description":"Rate limit exceeded (12 requests per minute)."},"500":{"description":"Generating or saving the image failed. Retry the request."}}}},"/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. New social content generation and edits also return 403 with code `app_marketing_not_allowed` when the app's owning workspace has the FERPA designation. Historical reads and resetting the plan remain available.</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/apps-api/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/apps-api/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/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/custom-domains/purchase-provider":{"get":{"summary":"Get domain purchase provider","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns which provider sells this app's workspace a new domain.\n\nBuying a domain happens in the Base44 editor, not through the API, so use this to know which checkout the editor shows. A domain bought there appears in [List custom domains](/api-reference/list-custom-domains) once the purchase completes.\n\nThis is limited to 60 requests a minute per caller. Some workspaces have a different limit.","operationId":"get_purchase_provider_api_apps__app_id__custom_domains_purchase_provider_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":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainPurchaseProvider"}}}},"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 reached. Retry later."}}}},"/api/apps/{app_id}/custom-domains/purchase-url":{"post":{"summary":"Create domain checkout link","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 one-time checkout link for buying a domain through Base44.\n\nNot every workspace can buy domains through the API. For one that can't, the call is rejected and the domain has to be bought in the Base44 editor.\n\nPass `domain` to open checkout on that domain, and check it first with [Check domain availability](/api-reference/check-domain-availability). Leave it out to open checkout on a domain search.\n\nThe person who opens the link pays at checkout, and nothing is bought until they finish. The domain is then attached to the app once it's registered, and appears in [List custom domains](/api-reference/list-custom-domains).\n\nEach call returns a new link and buys nothing, so retrying is safe, including after a timeout. The call fails while the checkout provider is unavailable.\n\nCreating links is limited to 6 requests a minute and 20 an hour per caller. Some workspaces have a different limit.","operationId":"create_domain_purchase_url_api_apps__app_id__custom_domains_purchase_url_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app you're finding a domain for.","title":"App Id"},"description":"ID of the app you're finding a domain for.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainPurchaseUrlRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainPurchaseUrl"}}}},"400":{"description":"`domain` isn't a valid domain name, or you have reached the limit of 350 custom domains."},"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."},"409":{"description":"This workspace can't buy domains through the API, so the purchase has to happen in the Base44 editor."},"429":{"description":"Rate limit reached. Retry later."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/custom-domains/availability":{"get":{"summary":"Check domain availability","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nChecks whether a domain can be registered through Base44, and suggests similar ones.\n\nThe first entry in `results` is always the domain you asked about, followed by up to five suggestions. A premium domain, or one containing a social media platform's name, comes back as unavailable because Base44 can't sell it.\n\nThe check asks Wix, so it fails while Wix's domain search is unavailable. Retrying later is safe.\n\nChecking is limited to 12 requests a minute per caller. Some workspaces have a different limit.","operationId":"get_domain_availability_api_apps__app_id__custom_domains_availability_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app you're finding a domain for.","title":"App Id"},"description":"ID of the app you're finding a domain for.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"domain","in":"query","required":true,"schema":{"type":"string","description":"Domain to check, such as `example.com`.","title":"Domain"},"description":"Domain to check, such as `example.com`.","example":"nordwind-furniture.com"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainAvailabilityCheck"}}}},"400":{"description":"`domain` isn't a valid domain name."},"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 reached. Retry later."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/custom-domains/registrations":{"get":{"summary":"List domain registrations","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 registration status of each domain bought through Base44 for the app, including when it expires and whether it renews.\n\nDomains you own elsewhere aren't included. Their registration lives with your own registrar.\n\nOnly a workspace owner can read registrations, because renewal is managed for the whole workspace.\n\nFields come back `null` when the registration can't be read, for example while the registrar is unavailable. That isn't a failure of the call, so read the value again later.\n\nRegistrations are ordered by domain name, with cursor-based pagination. To get the next page, send only the `cursor` from the previous response. It carries `limit` with it.\n\nReading registrations is limited to 30 requests a minute per caller. Some workspaces have a different limit.","operationId":"list_domain_registrations_api_apps__app_id__custom_domains_registrations_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":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"Items per page. Max 50.","default":50,"title":"Limit"},"description":"Items per page. Max 50.","example":50},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pagination cursor from previous response.","title":"Cursor"},"description":"Pagination cursor from previous response.","example":"gAAAAABn7vJ..."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainRegistrations"}}}},"400":{"description":"The `cursor` is invalid, expired, or from another request, or `limit` was sent beside it (`invalid_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"401":{"description":"Missing or invalid credentials (`unauthenticated`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"403":{"description":"You don't have access to this app, you aren't a workspace owner, or you used a workspace API key (`forbidden`). This endpoint takes a personal API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"404":{"description":"App not found (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"422":{"description":"A parameter is the wrong type or out of range (`invalid_request`). `error.details.errors` lists each problem.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"429":{"description":"Rate limit reached (`rate_limited`). Retry later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}}}}},"/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\nThe hostname stays reserved for your workspace after the delete. You can add it to another of your apps at any time; another workspace adding it must first prove it controls the DNS zone by publishing a TXT record Base44 hands them.\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."}}},"post":{"summary":"Add 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 a domain you own to the app. Nothing serves on it until you link it with [Link custom domain](/api-reference/link-custom-domain).\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. The domain's DNS has to point at Base44 before verification can pass, and the Base44 editor's domain settings show the records to set.\n\nIf a domain was released by another workspace, the call is rejected with a TXT record to publish. Publish it at the domain's DNS provider and send the same request again to prove you control the domain. A domain your own workspace released is re-added straight away.\n\nDon't retry a request that timed out without checking first. If the domain was added, a second request is rejected as already attached, so look for it in [List custom domains](/api-reference/list-custom-domains) before you send the request again.\n\nAdding domains is limited to 30 requests an hour per caller. Some workspaces have a different limit.\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_domain_api_apps__app_id__custom_domains_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"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDomainBody"}}}},"responses":{"200":{"description":"The domain, attached to the app but not yet linked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomDomainResource"}}}},"400":{"description":"The domain is already attached to an app, contains a social media platform's name, or you have reached the limit of 350 custom domains. Base44's hosting provider can also reject it."},"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."},"409":{"description":"Another workspace released this domain. Publish the TXT record in `detail.verification` and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainClaimChallenge"}}}},"422":{"description":"`domain` is missing or empty."},"429":{"description":"Rate limit reached, either yours or Base44's hosting provider's. Retry later."}}}},"/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}/dns-records":{"get":{"summary":"Get custom domain DNS 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 DNS records to create at the domain's DNS provider so the domain points at the app.\n\nSet `is_subdomain` to `true` for a subdomain such as `shop.example.com`, which takes one `CNAME`. A root domain such as `example.com` takes an `A` record for the domain itself and a `CNAME` for `www`.\n\nThis is for a domain you own elsewhere. A domain bought through Base44 is set up when it's bought, so asking for its records is rejected.\n\nAfter the records are in place, link the domain with [Link custom domain](/api-reference/link-custom-domain). DNS changes can take a while to spread, so verification can pass a while after that.\n\nRecords come back in a fixed order, the `A` record first, with cursor-based pagination. To get the next page, send only the `cursor` from the previous response. It carries `is_subdomain` and `limit` with it.\n\nReading records is limited to 60 requests a minute per caller. Some workspaces have a different limit.","operationId":"get_domain_dns_records_api_apps__app_id__custom_domains__domain_id__dns_records_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"},{"name":"is_subdomain","in":"query","required":false,"schema":{"type":"boolean","description":"Whether the domain is a subdomain such as `shop.example.com` (`true`) or a root domain such as `example.com` (`false`).","default":false,"title":"Is Subdomain"},"description":"Whether the domain is a subdomain such as `shop.example.com` (`true`) or a root domain such as `example.com` (`false`).","example":false},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"Items per page. Max 50.","default":50,"title":"Limit"},"description":"Items per page. Max 50.","example":50},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pagination cursor from previous response.","title":"Cursor"},"description":"Pagination cursor from previous response.","example":"gAAAAABn7vJ..."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainDnsRecords"}}}},"400":{"description":"The `cursor` is invalid, expired, or from another request, or other parameters were sent beside it (`invalid_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"401":{"description":"Missing or invalid credentials (`unauthenticated`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"402":{"description":"This workspace's plan doesn't include custom domains (`custom_domains_not_in_plan`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"403":{"description":"You don't have access to this app, or you used a workspace API key (`forbidden`). This endpoint takes a personal API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"404":{"description":"The app has no custom domain with this ID, or the app doesn't exist (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"409":{"description":"The domain was bought through Base44, so its DNS is already set up (`domain_purchased`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"422":{"description":"A parameter is the wrong type or out of range (`invalid_request`). `error.details.errors` lists each problem.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"429":{"description":"Rate limit reached (`rate_limited`). Retry later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}}}}},"/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"}}}},"400":{"description":"Base44's hosting provider rejected the status check. A domain that isn't linked fails this way."},"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}/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\nIf the check doesn't start, the domain's DNS usually doesn't point to Base44 yet. 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"}}}},"400":{"description":"Base44's hosting provider didn't start the check."},"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}/redirect":{"put":{"summary":"Set custom domain redirect","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSends every visitor of this domain to another of the app's domains with a 301 redirect, or stops redirecting when `target_domain` is `null`.\n\nThe target has to be another verified custom domain on the same app, and every redirect resolves in one hop. So the target can't already redirect somewhere else, this domain can't already be another domain's target, and two domains can't redirect to each other.\n\nSetting a redirect is limited to 30 requests an hour per caller. Some workspaces have a different limit.\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":"set_domain_redirect_api_apps__app_id__custom_domains__domain_id__redirect_put","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"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetDomainRedirectBody"}}}},"responses":{"200":{"description":"The domain, with its new redirect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomDomainResource"}}}},"400":{"description":"The domain is disabled, so it can't redirect."},"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 custom domain with this ID, or the app doesn't exist."},"422":{"description":"The target is this domain, isn't another verified custom domain on the app, or would make a redirect chain or loop."},"429":{"description":"Rate limit reached. Retry later."}}}},"/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/files/apps/{app_id}/upload":{"post":{"summary":"Upload app file","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nUploads a file to the app's storage and returns a link to it.\n\nSend the file as `multipart/form-data`. Use the public link of an uploaded image as `logo_url` in [Set app logo](/api-reference/set-app-logo) or as `social_image_url` in [Set social image](/api-reference/set-social-image).\n\nA public file gets a permanent link that anyone can open. A private file gets a signed link that expires after `expires_in` seconds, one hour at most, and this API has no way to get a new link for it. Upload a private file only when you use its link right away.\n\nThe size limit depends on the extension in the file name. For example, it's 40 MB for JPEG, PNG, GIF and WebP images, 5 MB for SVG, 100 MB for video and MP3 or WAV audio, and 10 MB for PDF and for any extension without its own limit. Executable and script files such as `.exe`, `.bat`, `.jar` and `.apk` are refused. Files are scanned for malware after the upload returns.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"upload_app_file_endpoint_api_files_apps__app_id__upload_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to upload the file to.","title":"App Id"},"description":"ID of the app to upload the file to.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","title":"UploadAppFile","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"The file to upload."},"visibility":{"type":"string","enum":["public","private"],"default":"public","description":"`public` for a permanent link anyone can open, or `private` for a signed link that expires."},"expires_in":{"type":"integer","minimum":60,"maximum":3600,"default":3600,"description":"Seconds until a private file's link expires. Ignored for a public file."}}}}}},"responses":{"200":{"description":"The stored file and its link.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadedAppFile"}}}},"400":{"description":"The file is empty, too large for its extension, of a refused type, or its size couldn't be determined."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, or your API key is read-only."},"404":{"description":"App not found."},"422":{"description":"`file` is missing, `visibility` isn't `public` or `private`, or `expires_in` is outside 60 to 3600."}}}},"/api/apps/{app_id}/comments/mentionable":{"get":{"summary":"List mentionable 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 the people a comment on the app can mention: the app's owner and everyone who can edit it, sorted by name.\n\nThis is limited to 600 requests per minute per app, shared by [List comments](/api-reference/list-comments) and [List mentionable users](/api-reference/list-mentionable-users). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit. Every request that counts against the limit gets `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix time in seconds) headers, and a `429` also gets `Retry-After` in seconds. If the limiter itself is unavailable, requests go through without these headers.\n\n<Note>Call this as an editor of the app, with a personal access token sent as a Bearer token or from a signed-in session. A read-only token works here. Workspace API keys aren't accepted.</Note>","operationId":"list_mentionable_users_api_apps__app_id__comments_mentionable_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":"The people a comment can mention, sorted by name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MentionableUsers"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or you used a workspace API key."},"404":{"description":"App not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded. Wait the number of seconds in `Retry-After` before retrying."}}}},"/api/apps/{app_id}/comments":{"get":{"summary":"List comments","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 open comment threads with all their comments, newest first. Set `include_resolved` to `true` to include resolved threads too.\n\nThreads are ordered by when they were created, newest first. A page holds up to `limit` threads, 500 by default. While `has_more` is `true`, request the next page with `cursor` set to `next_cursor`, and send nothing else beside it. A cursor works for 24 hours, only on this app. A thread created after you start paging shows up on a fresh first page, not in later pages.\n\nScreenshot links in the response work for one hour. Call this again for fresh ones, or use [Create comment screenshot link](/api-reference/create-comment-screenshot-link) for a link that lasts a year.\n\nThis is limited to 600 requests per minute per app, shared by [List comments](/api-reference/list-comments) and [List mentionable users](/api-reference/list-mentionable-users). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit. Every request that counts against the limit gets `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix time in seconds) headers, and a `429` also gets `Retry-After` in seconds. If the limiter itself is unavailable, requests go through without these headers.\n\n<Note>Call this as an editor of the app, with a personal access token sent as a Bearer token or from a signed-in session. A read-only token works here. Workspace API keys aren't accepted.</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_app_comments_api_apps__app_id__comments_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":"include_resolved","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Set to `true` to include resolved threads. Defaults to `false`.","title":"Include Resolved"},"description":"Set to `true` to include resolved threads. Defaults to `false`.","example":true},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":500,"minimum":1},{"type":"null"}],"description":"Threads per page, 1 to 500. Defaults to 500.","title":"Limit"},"description":"Threads per page, 1 to 500. Defaults to 500.","example":50},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`next_cursor` from the previous page. Send it on its own: it carries `include_resolved` and `limit`, so sending either beside it returns a `400`.","title":"Cursor"},"description":"`next_cursor` from the previous page. Send it on its own: it carries `include_resolved` and `limit`, so sending either beside it returns a `400`.","example":"gAAAAABn7vJ0q2kX"}],"responses":{"200":{"description":"One page of the app's comment threads, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentThreadList"}}}},"400":{"description":"`cursor` is invalid, expired, or from another app, or `include_resolved` or `limit` was sent beside it."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, or you used a workspace API key."},"404":{"description":"App not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded. Wait the number of seconds in `Retry-After` before retrying."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Create comment","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 new comment thread on the app with its first comment.\n\nSet `page_path` to the app page the comment is about. `anchor` pins the thread to an element of the app's preview. The builder fills it in when you click an element, and `source_location` must match that element's `data-source-location` attribute in the preview for a pin to show. Leave `anchor` out to post a thread with no pin. It still shows in the builder's comments panel.\n\nTo attach a screenshot, upload the image with [Upload app file](/api-reference/upload-app-file) and `visibility` set to `private`, then pass its `file_uri` as `screenshot_file_uri`. Only private files uploaded to this app are accepted.\n\n`mentioned_emails` records who the comment mentions. Only emails that [List mentionable users](/api-reference/list-mentionable-users) returns are kept, and the rest are dropped without an error. Mentioning someone sends them no email or notification, and responses never return the list.\n\nComments don't reach the builder agent on their own. It only works on a thread when someone sends the thread to the builder chat from the builder. Anyone with the app open in the builder sees the change right away.\n\nThis is limited to 120 requests per minute per app, shared by every other comments endpoint. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit. Every request that counts against the limit gets `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix time in seconds) headers, and a `429` also gets `Retry-After` in seconds. If the limiter itself is unavailable, requests go through without these headers.\n\n<Note>Call this as an editor of the app, with a personal access token sent as a Bearer token or from a signed-in session. Viewers in the app's workspace, read-only tokens, and workspace API keys are refused.</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":"create_app_comment_api_apps__app_id__comments_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/CreateAppCommentPayload"}}}},"responses":{"200":{"description":"The new thread with its first comment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentThreadItem"}}}},"400":{"description":"`screenshot_file_uri` isn't a private file uploaded to this app."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you're a viewer in the app's workspace, the app is blocked, or your token is read-only or a workspace API key."},"404":{"description":"App not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded. Wait the number of seconds in `Retry-After` before retrying."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/comments/threads/{thread_id}/replies":{"post":{"summary":"Reply to comment","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 reply to a comment thread. Replying to a resolved thread doesn't reopen it.\n\n`mentioned_emails` records who the comment mentions. Only emails that [List mentionable users](/api-reference/list-mentionable-users) returns are kept, and the rest are dropped without an error. Mentioning someone sends them no email or notification, and responses never return the list.\n\nAnyone with the app open in the builder sees the change right away.\n\nThis is limited to 120 requests per minute per app, shared by every other comments endpoint. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit. Every request that counts against the limit gets `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix time in seconds) headers, and a `429` also gets `Retry-After` in seconds. If the limiter itself is unavailable, requests go through without these headers.\n\n<Note>Call this as an editor of the app, with a personal access token sent as a Bearer token or from a signed-in session. Viewers in the app's workspace, read-only tokens, and workspace API keys are refused.</Note>","operationId":"reply_to_app_comment_api_apps__app_id__comments_threads__thread_id__replies_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"},{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","title":"Thread Id"},"description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","example":"68e2b7c1d4f0a9001c3e5a17"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplyToAppCommentPayload"}}}},"responses":{"200":{"description":"The new reply.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentMessage"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you're a viewer in the app's workspace, the app is blocked, or your token is read-only or a workspace API key."},"404":{"description":"App or comment thread not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded. Wait the number of seconds in `Retry-After` before retrying."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/comments/threads/{thread_id}/resolve":{"post":{"summary":"Resolve comment thread","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nMarks a comment thread as resolved. Any editor of the app can resolve any thread. Resolving a thread that's already resolved records the new time.\n\nResolved threads are left out of [List comments](/api-reference/list-comments) unless you set `include_resolved`. Reopen one with [Reopen comment thread](/api-reference/reopen-comment-thread). Anyone with the app open in the builder sees the change right away.\n\nThis is limited to 120 requests per minute per app, shared by every other comments endpoint. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit. Every request that counts against the limit gets `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix time in seconds) headers, and a `429` also gets `Retry-After` in seconds. If the limiter itself is unavailable, requests go through without these headers.\n\n<Note>Call this as an editor of the app, with a personal access token sent as a Bearer token or from a signed-in session. Viewers in the app's workspace, read-only tokens, and workspace API keys are refused.</Note>","operationId":"resolve_app_comment_thread_api_apps__app_id__comments_threads__thread_id__resolve_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"},{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","title":"Thread Id"},"description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","example":"68e2b7c1d4f0a9001c3e5a17"}],"responses":{"200":{"description":"The thread is resolved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolvedThread"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you're a viewer in the app's workspace, the app is blocked, or your token is read-only or a workspace API key."},"404":{"description":"App or comment thread not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded. Wait the number of seconds in `Retry-After` before retrying."}}}},"/api/apps/{app_id}/comments/threads/{thread_id}/unresolve":{"post":{"summary":"Reopen comment thread","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReopens a resolved comment thread. Any editor of the app can reopen any thread, and reopening an open thread changes nothing. Anyone with the app open in the builder sees the change right away.\n\nThis is limited to 120 requests per minute per app, shared by every other comments endpoint. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit. Every request that counts against the limit gets `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix time in seconds) headers, and a `429` also gets `Retry-After` in seconds. If the limiter itself is unavailable, requests go through without these headers.\n\n<Note>Call this as an editor of the app, with a personal access token sent as a Bearer token or from a signed-in session. Viewers in the app's workspace, read-only tokens, and workspace API keys are refused.</Note>","operationId":"unresolve_app_comment_thread_api_apps__app_id__comments_threads__thread_id__unresolve_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"},{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","title":"Thread Id"},"description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","example":"68e2b7c1d4f0a9001c3e5a17"}],"responses":{"200":{"description":"The thread is open.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReopenedThread"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you're a viewer in the app's workspace, the app is blocked, or your token is read-only or a workspace API key."},"404":{"description":"App or comment thread not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded. Wait the number of seconds in `Retry-After` before retrying."}}}},"/api/apps/{app_id}/comments/threads/{thread_id}/messages/{message_id}":{"patch":{"summary":"Edit comment","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 text of a comment you wrote and sets its `edited_at`. Only the author can edit a comment, so replies from the builder agent can't be edited.\n\n`mentioned_emails` records who the comment mentions. Only emails that [List mentionable users](/api-reference/list-mentionable-users) returns are kept, and the rest are dropped without an error. Mentioning someone sends them no email or notification, and responses never return the list. The list you send replaces the comment's previous mentions.\n\nThe response's `reactions` is always empty, even when the comment has reactions. Read them from [List comments](/api-reference/list-comments). Anyone with the app open in the builder sees the change right away.\n\nThis is limited to 120 requests per minute per app, shared by every other comments endpoint. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit. Every request that counts against the limit gets `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix time in seconds) headers, and a `429` also gets `Retry-After` in seconds. If the limiter itself is unavailable, requests go through without these headers.\n\n<Note>Call this as an editor of the app, with a personal access token sent as a Bearer token or from a signed-in session. Viewers in the app's workspace, read-only tokens, and workspace API keys are refused.</Note>","operationId":"edit_app_comment_message_api_apps__app_id__comments_threads__thread_id__messages__message_id__patch","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":"thread_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","title":"Thread Id"},"description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","example":"68e2b7c1d4f0a9001c3e5a17"},{"name":"message_id","in":"path","required":true,"schema":{"type":"string","description":"ID of a comment in the thread: `comment.id` for the first comment or a `replies[].id`, from [List comments](/api-reference/list-comments).","title":"Message Id"},"description":"ID of a comment in the thread: `comment.id` for the first comment or a `replies[].id`, from [List comments](/api-reference/list-comments).","example":"68e2b7c4d4f0a9001c3e5a1c"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditAppCommentPayload"}}}},"responses":{"200":{"description":"The edited comment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentMessage"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you're a viewer in the app's workspace, the app is blocked, or your token is read-only or a workspace API key, or you didn't write this comment."},"404":{"description":"App, comment thread, or comment not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded. Wait the number of seconds in `Retry-After` before retrying."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete comment reply","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 reply you wrote. Only the author can delete a reply, so replies from the builder agent can't be deleted on their own. To remove the first comment, delete the whole thread with [Delete comment thread](/api-reference/delete-comment-thread).\n\nDeleting a reply that's already gone returns a `404`. Anyone with the app open in the builder sees the change right away.\n\nThis is limited to 120 requests per minute per app, shared by every other comments endpoint. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit. Every request that counts against the limit gets `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix time in seconds) headers, and a `429` also gets `Retry-After` in seconds. If the limiter itself is unavailable, requests go through without these headers.\n\n<Note>Call this as an editor of the app, with a personal access token sent as a Bearer token or from a signed-in session. Viewers in the app's workspace, read-only tokens, and workspace API keys are refused.</Note>","operationId":"delete_app_comment_message_api_apps__app_id__comments_threads__thread_id__messages__message_id__delete","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":"thread_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","title":"Thread Id"},"description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","example":"68e2b7c1d4f0a9001c3e5a17"},{"name":"message_id","in":"path","required":true,"schema":{"type":"string","description":"ID of a comment in the thread: `comment.id` for the first comment or a `replies[].id`, from [List comments](/api-reference/list-comments).","title":"Message Id"},"description":"ID of a comment in the thread: `comment.id` for the first comment or a `replies[].id`, from [List comments](/api-reference/list-comments).","example":"68e2b7c4d4f0a9001c3e5a1c"}],"responses":{"200":{"description":"The reply is deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResult"}}}},"400":{"description":"The comment is the first comment of its thread. Delete the thread instead."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you're a viewer in the app's workspace, the app is blocked, or your token is read-only or a workspace API key, or you didn't write this comment."},"404":{"description":"App, comment thread, or comment not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded. Wait the number of seconds in `Retry-After` before retrying."}}}},"/api/apps/{app_id}/comments/threads/{thread_id}/messages/{message_id}/reactions":{"post":{"summary":"Toggle comment reaction","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nAdds your reaction with an emoji to a comment, or removes it when you already reacted with that emoji. Calling it twice undoes the first call, so don't retry it blindly.\n\nAny text of up to 16 characters is accepted as `emoji`. Anyone with the app open in the builder sees the change right away.\n\nThis is limited to 120 requests per minute per app, shared by every other comments endpoint. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit. Every request that counts against the limit gets `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix time in seconds) headers, and a `429` also gets `Retry-After` in seconds. If the limiter itself is unavailable, requests go through without these headers.\n\n<Note>Call this as an editor of the app, with a personal access token sent as a Bearer token or from a signed-in session. Viewers in the app's workspace, read-only tokens, and workspace API keys are refused.</Note>","operationId":"toggle_app_comment_reaction_api_apps__app_id__comments_threads__thread_id__messages__message_id__reactions_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"},{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","title":"Thread Id"},"description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","example":"68e2b7c1d4f0a9001c3e5a17"},{"name":"message_id","in":"path","required":true,"schema":{"type":"string","description":"ID of a comment in the thread: `comment.id` for the first comment or a `replies[].id`, from [List comments](/api-reference/list-comments).","title":"Message Id"},"description":"ID of a comment in the thread: `comment.id` for the first comment or a `replies[].id`, from [List comments](/api-reference/list-comments).","example":"68e2b7c4d4f0a9001c3e5a1c"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToggleReactionPayload"}}}},"responses":{"200":{"description":"Who reacts with the emoji now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReactionResult"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you're a viewer in the app's workspace, the app is blocked, or your token is read-only or a workspace API key."},"404":{"description":"App, comment thread, or comment not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded. Wait the number of seconds in `Retry-After` before retrying."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/comments/threads/{thread_id}/anchor":{"patch":{"summary":"Update comment anchor","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nMoves a thread's pin and replaces its screenshot. Only the person who started the thread can do this.\n\n`anchor` pins the thread to an element of the app's preview. The builder fills it in when you click an element, and `source_location` must match that element's `data-source-location` attribute in the preview for a pin to show. Leave `anchor` out to post a thread with no pin. It still shows in the builder's comments panel. The `anchor` you send replaces the old one in full.\n\nSend `screenshot_file_uri` to attach a new screenshot, or leave it out or send `null` to remove the screenshot. To attach a screenshot, upload the image with [Upload app file](/api-reference/upload-app-file) and `visibility` set to `private`, then pass its `file_uri` as `screenshot_file_uri`. Only private files uploaded to this app are accepted. Anyone with the app open in the builder sees the change right away.\n\nThis is limited to 120 requests per minute per app, shared by every other comments endpoint. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit. Every request that counts against the limit gets `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix time in seconds) headers, and a `429` also gets `Retry-After` in seconds. If the limiter itself is unavailable, requests go through without these headers.\n\n<Note>Call this as an editor of the app, with a personal access token sent as a Bearer token or from a signed-in session. Viewers in the app's workspace, read-only tokens, and workspace API keys are refused.</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":"update_app_comment_anchor_api_apps__app_id__comments_threads__thread_id__anchor_patch","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":"thread_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","title":"Thread Id"},"description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","example":"68e2b7c1d4f0a9001c3e5a17"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAnchorPayload"}}}},"responses":{"200":{"description":"The thread with its new pin.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentThread"}}}},"400":{"description":"`screenshot_file_uri` isn't a private file uploaded to this app."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you're a viewer in the app's workspace, the app is blocked, or your token is read-only or a workspace API key, or you didn't start this thread."},"404":{"description":"App or comment thread not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded. Wait the number of seconds in `Retry-After` before retrying."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/comments/threads/{thread_id}/read":{"post":{"summary":"Mark comment thread read","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nMarks a comment thread as read for you, up to its newest comment. The thread's `unread` in [List comments](/api-reference/list-comments) turns `false` until someone else comments again.\n\nThis is limited to 120 requests per minute per app, shared by every other comments endpoint. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit. Every request that counts against the limit gets `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix time in seconds) headers, and a `429` also gets `Retry-After` in seconds. If the limiter itself is unavailable, requests go through without these headers.\n\n<Note>Call this as an editor of the app, with a personal access token sent as a Bearer token or from a signed-in session. Viewers in the app's workspace, read-only tokens, and workspace API keys are refused.</Note>","operationId":"mark_app_comment_thread_read_api_apps__app_id__comments_threads__thread_id__read_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"},{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","title":"Thread Id"},"description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","example":"68e2b7c1d4f0a9001c3e5a17"}],"responses":{"200":{"description":"The thread is read for you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadResult"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you're a viewer in the app's workspace, the app is blocked, or your token is read-only or a workspace API key."},"404":{"description":"App or comment thread not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded. Wait the number of seconds in `Retry-After` before retrying."}}}},"/api/apps/{app_id}/comments/read-all":{"post":{"summary":"Mark all comments read","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nMarks the app's comment threads as read for you.\n\nIt covers the 500 newest open threads and the 500 newest resolved threads. On an app with more, older threads keep their `unread` state.\n\nThis is limited to 120 requests per minute per app, shared by every other comments endpoint. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit. Every request that counts against the limit gets `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix time in seconds) headers, and a `429` also gets `Retry-After` in seconds. If the limiter itself is unavailable, requests go through without these headers.\n\n<Note>Call this as an editor of the app, with a personal access token sent as a Bearer token or from a signed-in session. Viewers in the app's workspace, read-only tokens, and workspace API keys are refused.</Note>","operationId":"mark_all_app_comments_read_api_apps__app_id__comments_read_all_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":"The threads are read for you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadResult"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you're a viewer in the app's workspace, the app is blocked, or your token is read-only or a workspace API key."},"404":{"description":"App not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded. Wait the number of seconds in `Retry-After` before retrying."}}}},"/api/apps/{app_id}/comments/threads/{thread_id}/chat-screenshot-url":{"post":{"summary":"Create comment screenshot link","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 signed link to a thread's screenshot that works for one year, for pasting somewhere that outlives the one-hour links in [List comments](/api-reference/list-comments). Each call creates a new link, and a link can't be revoked before it expires.\n\nThe link opens the screenshot for anyone who has it, so share it only where that's fine.\n\nThis is limited to 120 requests per minute per app, shared by every other comments endpoint. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit. Every request that counts against the limit gets `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix time in seconds) headers, and a `429` also gets `Retry-After` in seconds. If the limiter itself is unavailable, requests go through without these headers.\n\n<Note>Call this as an editor of the app, with a personal access token sent as a Bearer token or from a signed-in session. Viewers in the app's workspace, read-only tokens, and workspace API keys are refused.</Note>","operationId":"create_chat_screenshot_url_api_apps__app_id__comments_threads__thread_id__chat_screenshot_url_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"},{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","title":"Thread Id"},"description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","example":"68e2b7c1d4f0a9001c3e5a17"}],"responses":{"200":{"description":"The link, or `null` when there's no screenshot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreenshotLink"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you're a viewer in the app's workspace, the app is blocked, or your token is read-only or a workspace API key."},"404":{"description":"App or comment thread not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded. Wait the number of seconds in `Retry-After` before retrying."}}}},"/api/apps/{app_id}/comments/threads/{thread_id}":{"delete":{"summary":"Delete comment thread","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 comment thread with all its replies and reactions. Any editor of the app can delete any thread, and it can't be undone.\n\nDeleting a thread that's already gone returns a `404`. Anyone with the app open in the builder sees the change right away.\n\nThis is limited to 120 requests per minute per app, shared by every other comments endpoint. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit. Every request that counts against the limit gets `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix time in seconds) headers, and a `429` also gets `Retry-After` in seconds. If the limiter itself is unavailable, requests go through without these headers.\n\n<Note>Call this as an editor of the app, with a personal access token sent as a Bearer token or from a signed-in session. Viewers in the app's workspace, read-only tokens, and workspace API keys are refused.</Note>","operationId":"delete_app_comment_thread_api_apps__app_id__comments_threads__thread_id__delete","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":"thread_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","title":"Thread Id"},"description":"ID of the comment thread, from `thread.id` in [List comments](/api-reference/list-comments).","example":"68e2b7c1d4f0a9001c3e5a17"}],"responses":{"200":{"description":"The thread is deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResult"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you're a viewer in the app's workspace, the app is blocked, or your token is read-only or a workspace API key."},"404":{"description":"App or comment thread not found."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded. Wait the number of seconds in `Retry-After` before retrying."}}}},"/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-trash/{app_id}/restore":{"post":{"summary":"Restore deleted app","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nTakes an app out of the trash, up to 30 days after [Delete app](/api-reference/delete-app) put it there.\n\nYou can restore an app you own, and a workspace admin can restore any app in the workspace. You must still be a member of the app's workspace, and the workspace must have room under its plan's app limit.\n\nThe app comes back with its entity records. If its owner left the workspace while it was in the trash, the workspace owner becomes its owner, and collaborators who left don't get their access back. If another app took its slug, it moves to a free one, returned as `new_slug`. Its backend functions are deployed again, and an app that was published goes live again.\n\n<Warning>Restoring doesn't reconnect the resources the delete released, such as custom domains and OAuth connections. Read `reconnect_needed` to see what to set up again.</Warning>\n\nThe whole restore runs before you get a response, so the call takes longer on an app with many backend functions. A restore can't be repeated: once the app is out of the trash, calling this again returns a `409`.\n\nThis is limited to 10 requests per minute. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"restore_api_apps_trash__app_id__restore_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to restore, as returned by [Delete app](/api-reference/delete-app).","title":"App Id"},"description":"ID of the app to restore, as returned by [Delete app](/api-reference/delete-app).","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The app is back, and what still needs reconnecting.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RestoreResult"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You neither own the app nor administer its workspace, you're no longer a member of its workspace, or your API key is read-only."},"404":{"description":"App not found, or it's outside the workspace or the apps your credential is allowed to reach."},"409":{"description":"The app can't be restored right now. It isn't in the trash, it was deleted more than 30 days ago or before restoring was available, the workspace is at its app limit, no free slug was found, or another restore or deploy of the app is in progress. The error message says which. Only the last case is worth retrying."},"429":{"description":"Rate limit exceeded. The base limit is 10 requests per minute. Wait the number of seconds in the `Retry-After` header before you retry."}}}},"/api/apps/platform/{app_id}/mcp-status":{"get":{"summary":"Get MCP status","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 live MCP server. That covers whether AI clients can connect to it now, its endpoint, how clients authenticate, and the tools it serves.\n\nThis reports what the last publish put live, not the config you're editing. Read that with [Get MCP config](/api-reference/get-mcp-config). A server is only active while the app is published, and a workspace rule can turn it off without a publish, which `workspace_policy` tells you.\n\nThis is limited to 120 requests a minute for each app. Some workspaces have a different limit.\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":"get_app_mcp_status_api_apps_platform__app_id__mcp_status_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":"The app's live MCP server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppMcpStatusResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, it doesn't exist, or your API key is read-only."},"404":{"description":"App not found, or it's outside the workspace your credential is scoped to."},"429":{"description":"Too many MCP status requests for this app in the last minute."}}}},"/api/apps/platform/{app_id}/mcp/config":{"get":{"summary":"Get MCP config","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 editable MCP config and the tools it resolves to.\n\nEvery entity and agent the app has is served unless the config holds it back, so `tools` lists each tool the next publish could serve, with `exposed` telling you whether the config serves it. `pending_removal` lists tools the live server still has that the config no longer produces. A publish removes them.\n\nThis is limited to 240 requests a minute for each app. Some workspaces have a different limit.\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":"get_app_mcp_config_api_apps_platform__app_id__mcp_config_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":"The app's MCP config and its tools.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppMcpConfigResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, it doesn't exist, or your API key is read-only."},"404":{"description":"App not found, or it's outside the workspace your credential is scoped to."},"429":{"description":"Too many MCP config reads for this app in the last minute."}}},"patch":{"summary":"Update MCP config","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 the app's MCP server authenticates clients, which tools it serves, or both. The app needs an MCP server first, which the builder sets up when you ask it to in chat.\n\nSend only the keys you want to change. `tools` replaces the stored `tools` object whole, so read the current one with [Get MCP config](/api-reference/get-mcp-config), change it, and send all of it back.\n\nChanges reach AI clients once you [deploy the app](/api-reference/deploy-an-app). The response shows the config as saved, and each tool's `published` tells you whether the live server already matches it.\n\nSwitching to `none` needs the app to be open to visitors without logging in. A workspace can also turn MCP off for its apps, or require `oauth`.\n\nThis is limited to 60 requests a minute for each app. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"patch_app_mcp_config_api_apps_platform__app_id__mcp_config_patch","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/AppMcpConfigPatch"}}}},"responses":{"200":{"description":"The config as saved, and its tools.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppMcpConfigResponse"}}}},"400":{"description":"The resulting config isn't valid. The detail lists each problem."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, it doesn't exist, or your API key is read-only. Also returned when the app's workspace has turned MCP off, or requires `oauth` and the config uses `none`."},"404":{"description":"App not found, or it's outside the workspace your credential is scoped to."},"409":{"description":"The app has no MCP server yet, or you're switching to `none` and the app requires visitors to log in."},"422":{"description":"The body isn't a JSON object, `auth` isn't `none` or `oauth`, or `tools` isn't an object."},"429":{"description":"Too many MCP config updates for this app in the last minute."}}}},"/api/workspace/members/bulk/role":{"put":{"summary":"Update workspace member roles in bulk","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 roles of up to 200 members at once, the same way as [Update workspace member role](/api-reference/update-workspace-member-role).\n\nYou can't change your own role or the owner's. A role that a group grants can only be changed at the group, with [Update workspace group](/api-reference/update-workspace-group) or by changing its members. `admin` needs the Business plan or higher. Moving someone to `viewer` removes their own credit limit, and promoting a guest makes them a full member.\n\nEvery item is checked before anything changes. If any item can't be applied, nothing changes and the request returns a `400` whose `detail.errors` lists each rejected item with its `email`, a `message`, and an `error_code` when there is one. A member who isn't in the workspace, or a change you couldn't make on the single-member endpoint, is reported there rather than as a `403` or `404`.\n\nA `200` has one entry in `results` per item, in request order. An item whose member changed while the request ran comes back with `status` `skipped` and wasn't applied. Count only `success` entries as done. A member whose role a group took over, or whose membership changed, is skipped and keeps their role.\n\nThis is limited to 5 requests per minute, shared by [Update workspace member roles in bulk](/api-reference/update-workspace-member-roles-in-bulk), [Remove workspace members in bulk](/api-reference/remove-workspace-members-in-bulk), and [Set member credit limits in bulk](/api-reference/set-member-credit-limits-in-bulk). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"bulk_update_member_role_api_workspace_members_bulk_role_put","parameters":[{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkUpdateMemberRoleRequest"}}}},"responses":{"200":{"description":"The outcome for each member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkMemberOperationResponse"}}}},"400":{"description":"At least one item can't be applied, and nothing changed. `detail.errors` lists each one.","content":{"application/json":{"example":{"detail":{"message":"Some members could not be updated. No changes were applied.","errors":[{"email":"sam@acme.com","error_code":"MEMBER_NOT_FOUND","message":"Member sam@acme.com not found in workspace 67e0b12c4d8a3f005b21c9e4"}]}}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/members/bulk/credit-limit":{"put":{"summary":"Set member credit limits in bulk","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSets or removes the credit limits of up to 200 members at once, the same way as [Set member credit limit](/api-reference/set-member-credit-limit).\n\nA limit can't be more than the workspace's monthly credits. Viewers can't have a limit, since they can't use credits, but you can remove one. The workspace owner is never limited.\n\nEvery item is checked before anything changes. If any item can't be applied, nothing changes and the request returns a `400` whose `detail.errors` lists each rejected item with its `email`, a `message`, and an `error_code` when there is one. A member who isn't in the workspace, or a change you couldn't make on the single-member endpoint, is reported there rather than as a `403` or `404`.\n\nA `200` has one entry in `results` per item, in request order. An item whose member changed while the request ran comes back with `status` `skipped` and wasn't applied. Count only `success` entries as done. A member who became a viewer, or whose membership changed, is skipped and keeps their limit.\n\nMember credit limits need the Business plan or higher. Below it, this returns a `402`.\n\nThis is limited to 5 requests per minute, shared by [Update workspace member roles in bulk](/api-reference/update-workspace-member-roles-in-bulk), [Remove workspace members in bulk](/api-reference/remove-workspace-members-in-bulk), and [Set member credit limits in bulk](/api-reference/set-member-credit-limits-in-bulk). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"bulk_update_member_credit_limit_api_workspace_members_bulk_credit_limit_put","parameters":[{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkUpdateMemberCreditLimitRequest"}}}},"responses":{"200":{"description":"The outcome for each member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkMemberOperationResponse"}}}},"400":{"description":"At least one item can't be applied, and nothing changed. `detail.errors` lists each one.","content":{"application/json":{"example":{"detail":{"message":"Some members could not be updated. No changes were applied.","errors":[{"email":"sam@acme.com","error_code":"MEMBER_NOT_FOUND","message":"Member sam@acme.com not found in workspace 67e0b12c4d8a3f005b21c9e4"}]}}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The workspace's plan doesn't include member credit limits."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/members/bulk/remove":{"post":{"summary":"Remove workspace members in bulk","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRemoves up to 200 people from a workspace at once, the same way as [Remove workspace member](/api-reference/remove-workspace-member).\n\nRemoving someone takes away their access to the workspace and its apps right away:\n\n- Apps they own move to the workspace owner, and their webhooks go to the owner too.\n- Their personal access tokens for the workspace are deleted, and integrations they connected in the workspace are disconnected.\n- They leave every custom group.\n- If this was the last workspace they could sign in to, their Base44 account is disabled.\n\nRemoving someone whose invitation is still pending cancels the invitation.\n\nYou can't remove yourself or the workspace owner.\n\nEvery item is checked before anything changes. If any item can't be applied, nothing changes and the request returns a `400` whose `detail.errors` lists each rejected item with its `email`, a `message`, and an `error_code` when there is one. A member who isn't in the workspace, or a change you couldn't make on the single-member endpoint, is reported there rather than as a `403` or `404`. A `200` lists every person removed in `results`, in request order. Retrying a request that succeeded returns a `400` listing those people as not found.\n\nPeople are handled one at a time before any membership is deleted. If a `409` stops the request, nobody is removed, but the people handled before the blocked one have already lost their app access and personal access tokens. Send the request again once the block clears.\n\nThis is limited to 5 requests per minute, shared by [Update workspace member roles in bulk](/api-reference/update-workspace-member-roles-in-bulk), [Remove workspace members in bulk](/api-reference/remove-workspace-members-in-bulk), and [Set member credit limits in bulk](/api-reference/set-member-credit-limits-in-bulk). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"bulk_remove_members_api_workspace_members_bulk_remove_post","parameters":[{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkRemoveMembersRequest"}}}},"responses":{"200":{"description":"Everyone was removed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkMemberOperationResponse"}}}},"400":{"description":"At least one item can't be applied, and nothing changed. `detail.errors` lists each one.","content":{"application/json":{"example":{"detail":{"message":"Some members could not be updated. No changes were applied.","errors":[{"email":"sam@acme.com","error_code":"MEMBER_NOT_FOUND","message":"Member sam@acme.com not found in workspace 67e0b12c4d8a3f005b21c9e4"}]}}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"409":{"description":"Your workspace requires an unlocked SSO session, or an app the person owns can't move to the owner right now: an ownership transfer is being accepted, a Google Business profile is being created, or the workspace has no active owner."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/members/{email}/credit-limit":{"put":{"summary":"Set member credit limit","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 most credits a member can use in a workspace each month, or removes their own limit when `credit_limit` is `null`. A member without their own limit gets the workspace default from [Set default member credit limit](/api-reference/set-default-member-credit-limit), if there is one.\n\nA limit can't be more than the workspace's monthly credits. Viewers can't have a limit, since they can't use credits, but you can remove one. The workspace owner is never limited. Setting the same limit again changes nothing, so retrying is safe.\n\nMember credit limits need the Business plan or higher. Below it, this returns a `402`.\n\nThis is limited to 30 requests per minute, shared with [Update workspace member role](/api-reference/update-workspace-member-role), [Remove workspace member](/api-reference/remove-workspace-member), [Set member credit limit](/api-reference/set-member-credit-limit), and [Set default member credit limit](/api-reference/set-default-member-credit-limit). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"update_member_credit_limit_api_workspace_members__email__credit_limit_put","parameters":[{"name":"email","in":"path","required":true,"schema":{"type":"string","description":"Email of the member whose limit to set, exactly as `email` in [List workspace members](/api-reference/list-workspace-members) returns it. Matching is exact, including case. URL-encode it: `@` as `%40` and `+` as `%2B`.","title":"Email"},"description":"Email of the member whose limit to set, exactly as `email` in [List workspace members](/api-reference/list-workspace-members) returns it. Matching is exact, including case. URL-encode it: `@` as `%40` and `+` as `%2B`.","example":"dana@acme.com"},{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMemberCreditLimitRequest"}}}},"responses":{"200":{"description":"The member's limit changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeResult"},"example":{"status":"success","message":"Credit limit updated for dana@acme.com."}}}},"400":{"description":"The limit is more than the workspace's monthly credits, or the member is a viewer."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The workspace's plan doesn't include member credit limits."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"404":{"description":"No member or pending invitation with this email in the workspace."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/default-credit-limit":{"put":{"summary":"Set default member credit limit","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 credit limit for every member of a workspace who doesn't have their own, or removes it when `default_credit_limit` is `null`. Members with their own limit keep it. [List workspace members](/api-reference/list-workspace-members) shows the default in a member's `credit_limit`, with `is_default_limit` set to `true`.\n\nThe default can't be more than the workspace's monthly credits. The workspace owner is never limited. Setting the same default again changes nothing, so retrying is safe.\n\nMember credit limits need the Business plan or higher. Below it, this returns a `402`.\n\nThis is limited to 30 requests per minute, shared with [Update workspace member role](/api-reference/update-workspace-member-role), [Remove workspace member](/api-reference/remove-workspace-member), [Set member credit limit](/api-reference/set-member-credit-limit), and [Set default member credit limit](/api-reference/set-default-member-credit-limit). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"update_default_credit_limit_api_workspace_default_credit_limit_put","parameters":[{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDefaultCreditLimitRequest"}}}},"responses":{"200":{"description":"The workspace default changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeResult"},"example":{"status":"success","message":"Default credit limit set to 300."}}}},"400":{"description":"The default is more than the workspace's monthly credits."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The workspace's plan doesn't include member credit limits."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/members/{email}/role":{"put":{"summary":"Update workspace member role","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 member's role in a workspace to `admin`, `editor`, or `viewer`. The new role applies right away. For someone whose invitation is still pending, it's the role they get when they accept.\n\nYou can't change your own role or the owner's. A role that a group grants can only be changed at the group, with [Update workspace group](/api-reference/update-workspace-group) or by changing its members. `admin` needs the Business plan or higher. Moving someone to `viewer` removes their own credit limit, and promoting a guest makes them a full member. Setting the role a member already has succeeds, so retrying is safe.\n\nThis is limited to 30 requests per minute, shared with [Update workspace member role](/api-reference/update-workspace-member-role), [Remove workspace member](/api-reference/remove-workspace-member), [Set member credit limit](/api-reference/set-member-credit-limit), and [Set default member credit limit](/api-reference/set-default-member-credit-limit). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"update_member_role_api_workspace_members__email__role_put","parameters":[{"name":"email","in":"path","required":true,"schema":{"type":"string","description":"Email of the member whose role to change, exactly as `email` in [List workspace members](/api-reference/list-workspace-members) returns it. Matching is exact, including case. URL-encode it: `@` as `%40` and `+` as `%2B`.","title":"Email"},"description":"Email of the member whose role to change, exactly as `email` in [List workspace members](/api-reference/list-workspace-members) returns it. Matching is exact, including case. URL-encode it: `@` as `%40` and `+` as `%2B`.","example":"dana@acme.com"},{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMemberRoleRequest"}}}},"responses":{"200":{"description":"The member has the new role.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeResult"},"example":{"status":"success","message":"Member role updated successfully."}}}},"400":{"description":"A group grants the member's role, or the member is a service account whose role can't change."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, your credential can't be used on this endpoint, you're changing your own role or the owner's, or `role` is `admin` and the workspace's plan doesn't allow admins."},"404":{"description":"No member or pending invitation with this email in the workspace."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/members/{email}":{"delete":{"summary":"Remove workspace member","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRemoves someone from a workspace.\n\nRemoving someone takes away their access to the workspace and its apps right away:\n\n- Apps they own move to the workspace owner, and their webhooks go to the owner too.\n- Their personal access tokens for the workspace are deleted, and integrations they connected in the workspace are disconnected.\n- They leave every custom group.\n- If this was the last workspace they could sign in to, their Base44 account is disabled.\n\nRemoving someone whose invitation is still pending cancels the invitation.\n\nYou can't remove yourself or the workspace owner. Retrying a removal that succeeded returns a `404`.\n\nThis is limited to 30 requests per minute, shared with [Update workspace member role](/api-reference/update-workspace-member-role), [Remove workspace member](/api-reference/remove-workspace-member), [Set member credit limit](/api-reference/set-member-credit-limit), and [Set default member credit limit](/api-reference/set-default-member-credit-limit). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"remove_member_api_workspace_members__email__delete","parameters":[{"name":"email","in":"path","required":true,"schema":{"type":"string","description":"Email of the member to remove, exactly as `email` in [List workspace members](/api-reference/list-workspace-members) returns it. Matching is exact, including case. URL-encode it: `@` as `%40` and `+` as `%2B`.","title":"Email"},"description":"Email of the member to remove, exactly as `email` in [List workspace members](/api-reference/list-workspace-members) returns it. Matching is exact, including case. URL-encode it: `@` as `%40` and `+` as `%2B`.","example":"dana@acme.com"},{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"responses":{"200":{"description":"The person is no longer in the workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeResult"},"example":{"status":"success","message":"Member removed successfully."}}}},"400":{"description":"You tried to remove yourself."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, your credential can't be used on this endpoint, or the person is the workspace owner."},"404":{"description":"No member or pending invitation with this email in the workspace."},"409":{"description":"Your workspace requires an unlocked SSO session, or an app the person owns can't move to the owner right now: an ownership transfer is being accepted, a Google Business profile is being created, or the workspace has no active owner."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/members/list":{"post":{"summary":"List workspace members","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 page of a workspace's members and pending invitations, with each person's role, credit use, credit limit, and the apps and agents they have.\n\nFilter with `roles`, `invite_statuses`, `credit_limit_set`, `apps_owned`, `agents_owned`, and `last_active`. A member must match every filter you set. `search` and `search_terms` match part of an email, name, role, or status, and a member matches when any term does.\n\nSet both `sort_by` and `sort_dir` to sort. Otherwise, and to break ties, joined members come before pending invitations, then by role from owner to guest, then by email. Sorting by `last_active` puts members who never used credits last in either direction.\n\nTo page, send the same body with `cursor` set to the previous response's `next_cursor`, until `has_more` is `false`. `total` counts every match. A `cursor` that Base44 didn't return starts again from the first page.\n\nOwners and admins see everyone's `credits_used` and `last_seen`. Other members see them only on their own row and get `null` for everyone else, and sorting by `credits` falls back to the default order for them. Active guests with a Base44 staff email aren't listed.\n\n<Warning>Results come from a cached copy that lasts 2 minutes for workspaces up to 200 people, 15 minutes up to 10,000, and 30 minutes above that, so `credits_used`, `last_active`, and `last_seen` can be that old. Changing members through Base44 starts a fresh copy, and positions in it can shift, so a page read with an older `cursor` can skip or repeat people. After you change members, page again from the start.</Warning>\n\nThis is limited to 30 requests per minute. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as any member of the workspace other than a guest, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. Read-only tokens work. Workspace API keys aren't accepted.</Note>","operationId":"list_members_api_workspace_members_list_post","parameters":[{"name":"workspaceId","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"description":"ID of the workspace. With a personal access token, use the token's workspace, which is also the default. From a signed-in session it defaults to your active workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace, which is also the default. From a signed-in session it defaults to your active workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MembersListRequest"}}}},"responses":{"200":{"description":"One page of members.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MembersListPage"}}}},"400":{"description":"You left out `workspaceId` and your session has no active workspace."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't a member of the workspace, you're a guest, your token is for a different workspace, or your credential can't be used on this endpoint."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/invitations/invite":{"post":{"summary":"Invite workspace member","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nInvites someone to a workspace by email. The invitee gets an email with a link to join, and a notification in Base44 if they already have an account. The link works for 60 days.\n\n`role` is the role they get when they accept. Set `group_id` to add them to a custom group when they accept.\n\nYou can't invite an existing member, or someone whose invitation is still pending. Send a pending invitation again with [Resend workspace invitation](/api-reference/resend-workspace-invitation). Inviting someone whose invitation expired sends a new one.\n\nA `200` means the invitation was saved. It doesn't confirm the email was sent: if sending fails, the invitation still stands and you can send it again with Resend workspace invitation. The response body is `null`.\n\nInviting is limited to 6 requests per minute, shared by [Invite workspace member](/api-reference/invite-workspace-member), [Invite workspace members in bulk](/api-reference/invite-workspace-members-in-bulk), [Resend workspace invitation](/api-reference/resend-workspace-invitation), and [Resend workspace invitations in bulk](/api-reference/resend-workspace-invitations-in-bulk). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"invite_workspace_member_api_workspace_invitations_invite_post","parameters":[{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteMemberRequest"}}}},"responses":{"200":{"description":"The invitation was saved. The body is `null`.","content":{"application/json":{"schema":{}}}},"400":{"description":"The person is already a member or already has a pending invitation, or `group_id` is an identity-provider group."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"`group_id` is set and the workspace's plan doesn't include groups."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, your credential can't be used on this endpoint, or `role` is `admin` and the workspace's plan doesn't allow admins."},"404":{"description":"`group_id` isn't a group in this workspace."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/invitations/bulk-invite":{"post":{"summary":"Invite workspace members in bulk","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nInvites up to 50 people to a workspace at once, the same way as [Invite workspace member](/api-reference/invite-workspace-member).\n\nThe whole request is refused when a `role` or `group_id` isn't allowed. Otherwise each invitation succeeds or fails on its own, so a `200` doesn't mean every invitation went out. Check `failed`, which lists existing members, people with a pending invitation, and invitations that couldn't be saved or emailed.\n\nRetrying is safe: invitations that went out the first time come back in `failed` as already pending.\n\nInviting is limited to 6 requests per minute, shared by [Invite workspace member](/api-reference/invite-workspace-member), [Invite workspace members in bulk](/api-reference/invite-workspace-members-in-bulk), [Resend workspace invitation](/api-reference/resend-workspace-invitation), and [Resend workspace invitations in bulk](/api-reference/resend-workspace-invitations-in-bulk). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"bulk_invite_workspace_members_api_workspace_invitations_bulk_invite_post","parameters":[{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkInviteRequest"}}}},"responses":{"200":{"description":"Which invitations went out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkInvitationResult"}}}},"400":{"description":"A `group_id` is an identity-provider group."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"A `group_id` is set and the workspace's plan doesn't include groups."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, your credential can't be used on this endpoint, or `role` is `admin` and the workspace's plan doesn't allow admins."},"404":{"description":"A `group_id` isn't a group in this workspace."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/invitations/resend":{"post":{"summary":"Resend workspace invitation","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 pending workspace invitation again, with a new link. The previous link stops working, and the new one works for 60 days. The invitation keeps its role and group.\n\nOnly a pending invitation can be resent, including one that expired. Each call sends another email, so retrying sends it again. Each invitation can be resent 3 times every 10 minutes.\n\nA `200` means the new link was saved. It doesn't confirm the email was sent. The response body is `null`.\n\nInviting is limited to 6 requests per minute, shared by [Invite workspace member](/api-reference/invite-workspace-member), [Invite workspace members in bulk](/api-reference/invite-workspace-members-in-bulk), [Resend workspace invitation](/api-reference/resend-workspace-invitation), and [Resend workspace invitations in bulk](/api-reference/resend-workspace-invitations-in-bulk). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"resend_workspace_invitation_api_workspace_invitations_resend_post","parameters":[{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResendInviteRequest"}}}},"responses":{"200":{"description":"The invitation has a new link. The body is `null`.","content":{"application/json":{"schema":{}}}},"400":{"description":"The person already joined the workspace."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, your credential can't be used on this endpoint, or the invitation is for `admin` and the workspace's plan no longer allows admins."},"404":{"description":"No pending invitation for this email in the workspace."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded, or this invitation was resent 3 times in the last 10 minutes."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/invitations/bulk-resend":{"post":{"summary":"Resend workspace invitations in bulk","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nResends up to 50 pending workspace invitations at once, the same way as [Resend workspace invitation](/api-reference/resend-workspace-invitation).\n\nEach invitation succeeds or fails on its own, so a `200` doesn't mean every invitation went out. Check `failed`, which lists emails with no pending invitation, people who already joined, invitations resent too often, invitations for `admin` when the plan no longer allows admins, and invitations that couldn't be saved or emailed. Each invitation can be resent 3 times every 10 minutes.\n\nInviting is limited to 6 requests per minute, shared by [Invite workspace member](/api-reference/invite-workspace-member), [Invite workspace members in bulk](/api-reference/invite-workspace-members-in-bulk), [Resend workspace invitation](/api-reference/resend-workspace-invitation), and [Resend workspace invitations in bulk](/api-reference/resend-workspace-invitations-in-bulk). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"bulk_resend_workspace_invitations_api_workspace_invitations_bulk_resend_post","parameters":[{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkResendInviteRequest"}}}},"responses":{"200":{"description":"Which invitations went out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkInvitationResult"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/{workspace_id}/connectors":{"get":{"summary":"List workspace connectors","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 connector set up in a workspace, with its OAuth client ID and scopes. The client secret is never returned.\n\nA workspace connector holds the workspace's own OAuth app for a connector, so the workspace's apps connect through it instead of through Base44's OAuth app. A connector can also run on Base44's OAuth app for the apps' users, when `credential_source` is `base44`.\n\nThe list isn't paginated.\n\nThis is limited to 120 requests per minute. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as any member of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token works. Workspace API keys aren't accepted.</Note>","operationId":"list_connectors_api_workspace__workspace_id__connectors_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspace Id"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"responses":{"200":{"description":"The workspace's connectors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationConnectorListResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't a member of the workspace, your token is for a different workspace, or your credential can't be used on this endpoint."},"409":{"description":"The workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}},"post":{"summary":"Create workspace connector","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 workspace connector.\n\nA workspace connector holds the workspace's own OAuth app for a connector, so the workspace's apps connect through it instead of through Base44's OAuth app. A connector can also run on Base44's OAuth app for the apps' users, when `credential_source` is `base44`.\n\nWith `credential_source` set to `byo`, send the client ID and secret of an OAuth app you registered in the provider's console, then register the redirect URIs from [Get OAuth redirect URIs](/api-reference/get-oauth-redirect-uris) there too. With `base44`, send neither: the connector runs on Base44's OAuth app, which needs a plan that includes Base44-managed connectors and accepts only the scopes that app is approved for.\n\nNames are unique in a workspace, so retrying a create that succeeded returns a `409`.\n\nThis is limited to 30 requests per minute, shared by [Create workspace connector](/api-reference/create-workspace-connector), [Update workspace connector](/api-reference/update-workspace-connector), and [Delete workspace connector](/api-reference/delete-workspace-connector). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused. Workspace API keys aren't accepted.</Note>","operationId":"create_connector_api_workspace__workspace_id__connectors_post","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspace Id"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrganizationConnectorRequest"}}}},"responses":{"201":{"description":"The new workspace connector.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationConnectorResponse"}}}},"400":{"description":"The connector can't be set up as a workspace connector, `client_secret` is missing for a connector that needs one, the connector has no Base44-managed option, a scope isn't available on Base44's OAuth app, or a `connection_config` value is missing or invalid."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"`credential_source` is `base44` and the workspace's plan doesn't include Base44-managed connectors."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"404":{"description":"The connector isn't available to you."},"409":{"description":"Another connector in the workspace has this name, or the workspace requires an unlocked SSO session."},"422":{"description":"A field has the wrong type or format, `client_id` is missing with `byo`, or `client_id` or `client_secret` was sent with `base44`."},"429":{"description":"Rate limit exceeded."}}}},"/api/workspace/{workspace_id}/connectors/{connector_id}":{"put":{"summary":"Update workspace connector","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 workspace connector. Send only the fields you want to change.\n\nSend `client_secret` to rotate the secret. Connections already made keep working. Send `scopes` to change what new connections ask for. Connections already made keep the scopes they were granted.\n\nTo move a connector from your own OAuth app onto Base44's, send `credential_source` set to `base44`. The stored client ID and secret are dropped, and connections the apps' users made through your OAuth app are revoked after the response returns, so they have to connect again. A connector on Base44's OAuth app can't move back, and its client ID, secret, and `uses_oauth_gateway` can't change.\n\nBefore setting `uses_oauth_gateway` to `true`, register the gateway redirect URI from [Get OAuth redirect URIs](/api-reference/get-oauth-redirect-uris) in the provider's console. Setting it back to `false` always works.\n\nThis is limited to 30 requests per minute, shared by [Create workspace connector](/api-reference/create-workspace-connector), [Update workspace connector](/api-reference/update-workspace-connector), and [Delete workspace connector](/api-reference/delete-workspace-connector). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused. Workspace API keys aren't accepted.</Note>","operationId":"update_connector_api_workspace__workspace_id__connectors__connector_id__put","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspace Id"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"},{"name":"connector_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workspace connector to update, from `id` in [List workspace connectors](/api-reference/list-workspace-connectors).","title":"Connector Id"},"description":"ID of the workspace connector to update, from `id` in [List workspace connectors](/api-reference/list-workspace-connectors).","example":"6820f3a4e7b91d003c45a1f9"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateOrganizationConnectorRequest"}}}},"responses":{"200":{"description":"The updated workspace connector.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationConnectorResponse"}}}},"400":{"description":"The change isn't allowed for a connector on Base44's OAuth app, `client_secret` is blank for a connector that needs one, a scope isn't available on Base44's OAuth app, a `connection_config` value is missing or invalid, or the connector can no longer be set up as a workspace connector."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"`credential_source` is `base44` and the workspace's plan doesn't include Base44-managed connectors."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, your credential can't be used on this endpoint, or `uses_oauth_gateway` is `true` and the gateway callback isn't available to you."},"404":{"description":"Workspace connector not found in this workspace, or its connector isn't available to you."},"409":{"description":"Another connector in the workspace has this name, another request just moved the connector onto Base44's OAuth app, or the workspace requires an unlocked SSO session."},"422":{"description":"A field has the wrong type or format, or is empty."},"429":{"description":"Rate limit exceeded."}}},"delete":{"summary":"Delete workspace connector","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 workspace connector. Apps and their users can no longer connect through it.\n\nFor a connector on Base44's OAuth app, every connection the apps' users made through it is revoked with the provider after the response returns. Connections made through the workspace's own OAuth app aren't revoked with the provider. Owners of apps that used the connector get a notification.\n\nDeleting a connector that's already gone returns a `404`.\n\nThis is limited to 30 requests per minute, shared by [Create workspace connector](/api-reference/create-workspace-connector), [Update workspace connector](/api-reference/update-workspace-connector), and [Delete workspace connector](/api-reference/delete-workspace-connector). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused. Workspace API keys aren't accepted.</Note>","operationId":"delete_connector_api_workspace__workspace_id__connectors__connector_id__delete","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspace Id"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"},{"name":"connector_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workspace connector to delete, from `id` in [List workspace connectors](/api-reference/list-workspace-connectors).","title":"Connector Id"},"description":"ID of the workspace connector to delete, from `id` in [List workspace connectors](/api-reference/list-workspace-connectors).","example":"6820f3a4e7b91d003c45a1f9"}],"responses":{"204":{"description":"The workspace connector was deleted."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"404":{"description":"Workspace connector not found in this workspace."},"409":{"description":"The workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/apps/{app_id}/oauth-redirect-uris":{"get":{"summary":"Get OAuth redirect URIs","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 redirect URIs to register in the provider's console for a workspace connector that uses your own OAuth app, so its connections from this app can complete.\n\nWhich ones you need depends on the connector's `uses_oauth_gateway` in [List workspace connectors](/api-reference/list-workspace-connectors):\n- `true`: register `gateway_redirect_uri` only. It stays the same for every app.\n- `false`: register `builder_redirect_uri` and every `uri` in `app_redirect_uris`. The list grows when the app gets a new custom domain, so register the new host's URI too.\n\nConnectors that run on Base44's OAuth app need none of these. Anyone who can open the app in the builder can call this, including viewers.\n\nThis is limited to 120 requests per minute per caller. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal access token for the app's workspace, including a read-only token, or a signed-in session. Workspace API keys are not accepted.</Note>","operationId":"get_app_redirect_uris_api_apps__app_id__oauth_redirect_uris_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to get redirect URIs for.","title":"App Id"},"description":"ID of the app to get redirect URIs for.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The redirect URIs for the app.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppRedirectURIsResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't open this app in the builder."},"404":{"description":"App not found, or your token is for a different workspace or doesn't include the app."},"409":{"description":"The app's workspace requires an unlocked SSO session."},"429":{"description":"Rate limit reached. Retry later."}}}},"/api/workspace/{workspace_id}/connector-governance":{"get":{"summary":"Get workspace connector policies","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 connector policies of a workspace, one entry per connector available to it.\n\nA connector policy has two switches. `builder_connection_enabled` decides whether builders can connect the connector to the workspace's apps. `per_user_connection_enabled` decides whether the apps' users can connect their own accounts through a workspace connector. [Get connector policies](/api-reference/get-connector-policies) returns the same list for an app's workspace.\n\nThe list isn't paginated.\n\nThis is limited to 120 requests per minute. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as any member of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token works. Workspace API keys aren't accepted.</Note>","operationId":"list_connector_governance_api_workspace__workspace_id__connector_governance_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspace Id"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"responses":{"200":{"description":"The workspace's connector policies.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorGovernanceListResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't a member of the workspace, your token is for a different workspace, or your credential can't be used on this endpoint."},"409":{"description":"The workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/workspace/{workspace_id}/connector-governance/{integration_type}/builder-connection":{"put":{"summary":"Update builder connection policy","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nTurns builder connections for a connector on or off in a workspace.\n\nA connector policy has two switches. `builder_connection_enabled` decides whether builders can connect the connector to the workspace's apps. `per_user_connection_enabled` decides whether the apps' users can connect their own accounts through a workspace connector.\n\nTurning a switch off also stops apps from using connections already made that way, until it's turned back on. Owners of affected apps get a notification.\n\nSetting the current value again changes nothing. Changing policies needs a plan that includes connector policies.\n\nThis is limited to 60 requests per minute, shared by [Update builder connection policy](/api-reference/update-builder-connection-policy), [Update per-user connection policy](/api-reference/update-per-user-connection-policy), and [Update workspace connector policies in bulk](/api-reference/update-workspace-connector-policies-in-bulk). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused. Workspace API keys aren't accepted.</Note>","operationId":"toggle_builder_connection_api_workspace__workspace_id__connector_governance__integration_type__builder_connection_put","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspace Id"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"},{"name":"integration_type","in":"path","required":true,"schema":{"type":"string","description":"ID of the connector, from `integration_type` in [Get workspace connector policies](/api-reference/get-workspace-connector-policies).","title":"Integration Type"},"description":"ID of the connector, from `integration_type` in [Get workspace connector policies](/api-reference/get-workspace-connector-policies).","example":"googlecalendar"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateConnectionToggleRequest"}}}},"responses":{"200":{"description":"The connector's policy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorGovernanceItem"}}}},"400":{"description":"The connector isn't available to the workspace, or it has no builder connections."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The workspace's plan doesn't include connector policies."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"409":{"description":"The workspace requires an unlocked SSO session."},"422":{"description":"`enabled` is missing or isn't a boolean."},"429":{"description":"Rate limit exceeded."}}}},"/api/workspace/{workspace_id}/connector-governance/{integration_type}/per-user-connection":{"put":{"summary":"Update per-user connection policy","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nTurns per-user connections for a connector on or off in a workspace.\n\nA connector policy has two switches. `builder_connection_enabled` decides whether builders can connect the connector to the workspace's apps. `per_user_connection_enabled` decides whether the apps' users can connect their own accounts through a workspace connector.\n\nTurning it on needs a workspace connector for the connector, from [Create workspace connector](/api-reference/create-workspace-connector). Turning a switch off also stops apps from using connections already made that way, until it's turned back on. Owners of affected apps get a notification.\n\nSetting the current value again changes nothing. Changing this switch needs a plan that includes connector policies, except for connectors whose `connector_mode` is `byo_shared_only` or `byo_shared_and_app_user`.\n\nThis is limited to 60 requests per minute, shared by [Update builder connection policy](/api-reference/update-builder-connection-policy), [Update per-user connection policy](/api-reference/update-per-user-connection-policy), and [Update workspace connector policies in bulk](/api-reference/update-workspace-connector-policies-in-bulk). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused. Workspace API keys aren't accepted.</Note>","operationId":"toggle_per_user_connection_api_workspace__workspace_id__connector_governance__integration_type__per_user_connection_put","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspace Id"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"},{"name":"integration_type","in":"path","required":true,"schema":{"type":"string","description":"ID of the connector, from `integration_type` in [Get workspace connector policies](/api-reference/get-workspace-connector-policies).","title":"Integration Type"},"description":"ID of the connector, from `integration_type` in [Get workspace connector policies](/api-reference/get-workspace-connector-policies).","example":"googlecalendar"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateConnectionToggleRequest"}}}},"responses":{"200":{"description":"The connector's policy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorGovernanceItem"}}}},"400":{"description":"The connector isn't available to the workspace, it has no per-user connections, or you turned them on without a workspace connector for it."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The workspace's plan doesn't include connector policies, and the connector needs it."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"409":{"description":"The workspace requires an unlocked SSO session."},"422":{"description":"`enabled` is missing or isn't a boolean."},"429":{"description":"Rate limit exceeded."}}}},"/api/workspace/{workspace_id}/connector-governance/bulk":{"put":{"summary":"Update workspace connector policies in bulk","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 policies of up to 100 connectors in a workspace at once.\n\nA connector policy has two switches. `builder_connection_enabled` decides whether builders can connect the connector to the workspace's apps. `per_user_connection_enabled` decides whether the apps' users can connect their own accounts through a workspace connector. Each action sets either switch, or both. Leave a switch out, or send `null`, to keep it.\n\nActions run in order, and each succeeds or fails on its own, so a `200` doesn't mean every action applied. Check `ok` on each result. A failed action changes nothing, except one that fails while saving: its first switch may already be saved, so read the policies again with [Get workspace connector policies](/api-reference/get-workspace-connector-policies).\n\nTurning a switch off also stops apps from using connections already made that way, until it's turned back on. Owners of affected apps get a notification.\n\nChanging policies here needs a plan that includes connector policies, for every connector.\n\nThis is limited to 60 requests per minute, shared by [Update builder connection policy](/api-reference/update-builder-connection-policy), [Update per-user connection policy](/api-reference/update-per-user-connection-policy), and [Update workspace connector policies in bulk](/api-reference/update-workspace-connector-policies-in-bulk). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused. Workspace API keys aren't accepted.</Note>","operationId":"bulk_toggle_api_workspace__workspace_id__connector_governance_bulk_put","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspace Id"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkToggleRequest"}}}},"responses":{"200":{"description":"The outcome of each action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkToggleResponse"}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The workspace's plan doesn't include connector policies."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"409":{"description":"The workspace requires an unlocked SSO session."},"422":{"description":"`actions` has more than 100 items, or an action has a missing or wrongly typed field."},"429":{"description":"Rate limit exceeded."}}}},"/api/workspace/{workspace_id}/brands":{"get":{"summary":"List workspace brands","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 brand in a workspace, with the details a brand card shows. The list isn't paginated. Get a brand's full kit and design system with [Get workspace brand](/api-reference/get-workspace-brand).\n\n`is_default` marks the workspace's default brand. The default brand is the one Base44 selects for you when you start a new app.\n\nThis is limited to 300 requests per minute, shared with [Get workspace brand](/api-reference/get-workspace-brand). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as any member of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused even though this endpoint only reads, and workspace API keys aren't accepted.</Note>","operationId":"list_brands_api_workspace__workspace_id__brands_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspace Id"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"responses":{"200":{"description":"The workspace's brands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceBrandList"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't a member of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}},"post":{"summary":"Create workspace brand","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 brand you already have to a workspace, from its kit and, optionally, its design system. The brand is `ready` right away. To have Base44 build a brand from a description instead, use [Generate workspace brand](/api-reference/generate-workspace-brand).\n\n`kit.title` becomes the brand's title, and it can't exactly match another brand's title in the workspace. `kit` and `design_system` are stored as you send them, up to 1 MB together. If the workspace has no default brand, this brand becomes the default.\n\nRetrying a create that succeeded returns a `409`, because the title is then taken, or the plan-limit `403` when the new brand filled the workspace's limit.\n\nBrands need the Builder plan or higher. An Enterprise workspace can have any number of brands, and any other workspace on Builder or a higher plan can have 1. A brand whose `status` is `failed` doesn't count. Over the limit this returns a `403` whose `detail.reason` is `design_system_limit_reached` and whose `detail.limit` is the workspace's limit.\n\nThis is limited to 60 requests per minute, shared with the other endpoints that change brands, except generating one. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"create_brand_api_workspace__workspace_id__brands_post","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspace Id"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandUpsertRequest"}}}},"responses":{"200":{"description":"The new brand.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceBrandStarted"},"example":{"status":"success","message":"Brand 'Nordwind' added","id":"68e0c4a1b9d2f7003a5e1b42"}}}},"400":{"description":"`kit` has no `title` or no `description`, or `kit` and `design_system` together are larger than 1 MB."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, your credential can't be used on this endpoint, or the workspace is at its brand limit."},"409":{"description":"Another brand in the workspace already has this title, or your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/{workspace_id}/brands/generate":{"post":{"summary":"Generate workspace brand","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nStarts generating a brand from a text description, and returns the new brand's `id` right away. AI models build the brand's kit and design system in the background.\n\nThe brand starts with `status` `generating` and the title `Generating…`. Poll [Get workspace brand](/api-reference/get-workspace-brand) every few seconds until `status` is `ready` or `failed`. When generation fails for a reason you can fix, `failure_message` says why. The finished brand's title gets a number added, such as `Nordwind 2`, when another brand already has it. If the workspace has no default brand, this brand becomes the default.\n\nSend a `description` with text in it. A blank one still returns an `id`, and that brand then ends as `failed`.\n\nStarting a generation deletes the workspace's brands whose `status` is `failed`. Retrying a call that succeeded starts a second generation, or returns the plan-limit `403` when the first one filled the workspace's limit.\n\nBrands need the Builder plan or higher. An Enterprise workspace can have any number of brands, and any other workspace on Builder or a higher plan can have 1. A brand whose `status` is `failed` doesn't count. Over the limit this returns a `403` whose `detail.reason` is `design_system_limit_reached` and whose `detail.limit` is the workspace's limit.\n\nThis is limited to 20 requests per minute per user, counted across your signed-in sessions and personal access tokens. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"generate_brand_api_workspace__workspace_id__brands_generate_post","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspace Id"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandDescriptionRequest"}}}},"responses":{"200":{"description":"Generation started. Poll the brand by `id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceBrandStarted"},"example":{"status":"success","message":"Generating brand","id":"68e0c4a1b9d2f7003a5e1b42"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, your credential can't be used on this endpoint, or the workspace is at its brand limit."},"409":{"description":"Another generation started in the workspace at the same moment, so try again, or your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/{workspace_id}/brands/{brand_id}":{"get":{"summary":"Get workspace brand","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 brand with its full kit and design system.\n\nWhile a brand is being generated, `kit` and `design_system` are empty. Poll this endpoint every few seconds until `status` is `ready` or `failed`, and read `generation_step` and `preview` in the meantime.\n\nThis is limited to 300 requests per minute, shared with [List workspace brands](/api-reference/list-workspace-brands). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as any member of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused even though this endpoint only reads, and workspace API keys aren't accepted.</Note>","operationId":"get_brand_api_workspace__workspace_id__brands__brand_id__get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspace Id"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"},{"name":"brand_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the brand to get. Get it from `id` in [List workspace brands](/api-reference/list-workspace-brands).","title":"Brand Id"},"description":"ID of the brand to get. Get it from `id` in [List workspace brands](/api-reference/list-workspace-brands).","example":"68e0c4a1b9d2f7003a5e1b42"}],"responses":{"200":{"description":"The brand.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceBrand"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't a member of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"404":{"description":"Brand not found in this workspace."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}},"put":{"summary":"Update workspace brand","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 brand's kit and design system. Send the whole brand: `design_system` replaces the stored one, and leaving it out clears it.\n\n`kit.title` becomes the brand's title, and it can't exactly match another brand's title in the workspace. `kit` and `design_system` are stored as you send them, up to 1 MB together.\n\nUpdating doesn't change the brand's `status`. Don't update a brand while it's `generating`, because the finished generation replaces your changes.\n\nThis is limited to 60 requests per minute, shared with the other endpoints that change brands, except generating one. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</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":"update_brand_api_workspace__workspace_id__brands__brand_id__put","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspace Id"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"},{"name":"brand_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the brand to update. Get it from `id` in [List workspace brands](/api-reference/list-workspace-brands).","title":"Brand Id"},"description":"ID of the brand to update. Get it from `id` in [List workspace brands](/api-reference/list-workspace-brands).","example":"68e0c4a1b9d2f7003a5e1b42"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandUpsertRequest"}}}},"responses":{"200":{"description":"The brand was saved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeResult"},"example":{"status":"success","message":"Brand 'Nordwind' saved"}}}},"400":{"description":"`kit` has no `title` or no `description`, or `kit` and `design_system` together are larger than 1 MB."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"404":{"description":"Brand not found in this workspace."},"409":{"description":"Another brand in the workspace already has this title, or your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete workspace brand","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 brand. This can't be undone.\n\nEvery app in the workspace that uses the brand stops using it. If the brand is the default, the workspace is left with no default brand. Deleting a brand that's still `generating` discards the generation.\n\nThis is limited to 60 requests per minute, shared with the other endpoints that change brands, except generating one. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</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_brand_api_workspace__workspace_id__brands__brand_id__delete","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspace Id"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"},{"name":"brand_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the brand to delete. Get it from `id` in [List workspace brands](/api-reference/list-workspace-brands).","title":"Brand Id"},"description":"ID of the brand to delete. Get it from `id` in [List workspace brands](/api-reference/list-workspace-brands).","example":"68e0c4a1b9d2f7003a5e1b42"}],"responses":{"200":{"description":"The brand was deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeResult"},"example":{"status":"success","message":"Brand 'Nordwind' removed"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"404":{"description":"Brand not found in this workspace."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/workspace/{workspace_id}/brands/{brand_id}/default":{"put":{"summary":"Set default workspace brand","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nMakes a brand the workspace's default, in place of any other. The default brand is the one Base44 selects for you when you start a new app.\n\nA brand whose `status` is `generating` or `failed` can't be the default. Setting the brand that's already the default changes nothing.\n\nThis is limited to 60 requests per minute, shared with the other endpoints that change brands, except generating one. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</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":"set_default_brand_api_workspace__workspace_id__brands__brand_id__default_put","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspace Id"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"},{"name":"brand_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the brand to make the default. Get it from `id` in [List workspace brands](/api-reference/list-workspace-brands).","title":"Brand Id"},"description":"ID of the brand to make the default. Get it from `id` in [List workspace brands](/api-reference/list-workspace-brands).","example":"68e0c4a1b9d2f7003a5e1b42"}],"responses":{"200":{"description":"The brand is the workspace's default.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeResult"},"example":{"status":"success","message":"'Nordwind' is now the default brand"}}}},"400":{"description":"The brand's `status` is `generating` or `failed`."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"404":{"description":"Brand not found in this workspace."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}},"delete":{"summary":"Clear default workspace brand","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 brand being the workspace's default, so the workspace has no default brand. If the brand isn't the default, nothing changes and this still succeeds.\n\nThis is limited to 60 requests per minute, shared with the other endpoints that change brands, except generating one. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</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":"clear_default_brand_api_workspace__workspace_id__brands__brand_id__default_delete","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspace Id"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"},{"name":"brand_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the brand to stop being the default. Get it from `id` in [List workspace brands](/api-reference/list-workspace-brands).","title":"Brand Id"},"description":"ID of the brand to stop being the default. Get it from `id` in [List workspace brands](/api-reference/list-workspace-brands).","example":"68e0c4a1b9d2f7003a5e1b42"}],"responses":{"200":{"description":"The brand isn't the workspace's default.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeResult"},"example":{"status":"success","message":"'Nordwind' is no longer the default brand"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"404":{"description":"Brand not found in this workspace."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/workspace/groups":{"get":{"summary":"List workspace groups","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 group in a workspace, oldest first, with how many members each has. The list isn't paginated.\n\nEvery member of a group gets the group's `role`, and a member of several groups gets the highest of their roles. A group whose `role` is `no_access` blocks members who get their role only from it. An `idp` group grants its role only while the workspace uses identity-provider groups. Otherwise its role is stored but grants nothing.\n\nThis is limited to 120 requests per minute, shared with [List group members](/api-reference/list-group-members). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner, admin, or editor of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused even though this endpoint only reads, and workspace API keys aren't accepted.</Note>","operationId":"list_groups_api_workspace_groups_get","parameters":[{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"responses":{"200":{"description":"The workspace's groups.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceGroupListResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner, admin, or editor of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Create workspace group","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 custom group in a workspace, optionally with a role and its first members.\n\nEvery member of a group gets the group's `role`, and a member of several groups gets the highest of their roles. A group whose `role` is `no_access` blocks members who get their role only from it. Leave `role` out for a group that grants no role.\n\nOnly active members of the workspace can join a group. Pending invitees, guests, and the workspace owner can't. To add someone who isn't a member yet, invite them with [Invite workspace member](/api-reference/invite-workspace-member) and pass `group_id`. If any address in `member_emails` can't be added, nothing is created.\n\nGroup names are unique within a workspace, ignoring case, so retrying a create that succeeded returns a `400`. You can't make a change that removes your own admin access.\n\nThe response's `unresolved_count` is always `0`.\n\nGroups need the Business plan or higher. Below it, this returns a `402`.\n\nThis is limited to 60 requests per minute, shared with the other endpoints that change groups or their members. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"create_group_api_workspace_groups_post","parameters":[{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGroupRequest"}}}},"responses":{"200":{"description":"The new group.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceGroupSummary"}}}},"400":{"description":"A custom group with this name already exists, an address in `member_emails` isn't an active workspace member, or the change would remove your own admin access."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The workspace's plan doesn't include groups."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/groups/{group_id}":{"put":{"summary":"Update workspace group","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRenames a group, changes its description, or changes the role it grants. Send only the fields you want to change.\n\nSet `role` to `null` to clear it, so the group grants no role. Leave `role` out to keep it. A new role on a custom group applies to every member right away.\n\nA group whose `source` is `idp` gets its name and members from your identity provider, so they can't be changed here. Its role and description can still change. An `idp` group grants its role only while the workspace uses identity-provider groups. Otherwise its role is stored but grants nothing. You can't make a change that removes your own admin access.\n\nThe response's `unresolved_count` is always `0`. Read the real count from [List workspace groups](/api-reference/list-workspace-groups).\n\nGroups need the Business plan or higher. Below it, this returns a `402`.\n\nThis is limited to 60 requests per minute, shared with the other endpoints that change groups or their members. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"update_group_api_workspace_groups__group_id__put","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the group to update. Get it from `id` in [List workspace groups](/api-reference/list-workspace-groups).","title":"Group Id"},"description":"ID of the group to update. Get it from `id` in [List workspace groups](/api-reference/list-workspace-groups).","example":"68b4e1d2c9a7f3001e2d4b61"},{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGroupRequest"}}}},"responses":{"200":{"description":"The updated group.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceGroupSummary"}}}},"400":{"description":"Another custom group already has this name, you tried to rename an identity-provider group, or the change would remove your own admin access."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The workspace's plan doesn't include groups."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"404":{"description":"Group not found in this workspace."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete workspace group","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 group. Its members lose the role it granted, and apps shared with the group stop being shared through it.\n\nEach affected member's role is worked out again from their other groups. A member left in no group becomes a viewer, or gets the workspace's SSO default role when SSO is set up.\n\nYou can't make a change that removes your own admin access. Deleting works on any plan, so you can take groups apart after a downgrade.\n\nThis is limited to 60 requests per minute, shared with the other endpoints that change groups or their members. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"delete_group_api_workspace_groups__group_id__delete","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the group to delete. Get it from `id` in [List workspace groups](/api-reference/list-workspace-groups).","title":"Group Id"},"description":"ID of the group to delete. Get it from `id` in [List workspace groups](/api-reference/list-workspace-groups).","example":"68b4e1d2c9a7f3001e2d4b61"},{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"responses":{"200":{"description":"The group was deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeResult"},"example":{"status":"success","message":"Group deleted."}}}},"400":{"description":"Deleting the group would remove your own admin access."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"404":{"description":"Group not found in this workspace."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/groups/{group_id}/members":{"get":{"summary":"List group members","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 page of a group's members, sorted by email. Set `search` to match part of a member's email or name.\n\nTo page, keep requesting with `offset` increased by the number of members returned while `has_more` is `true`.\n\nMembers your identity provider sent who don't have a Base44 account in the workspace yet aren't listed. [List workspace groups](/api-reference/list-workspace-groups) counts them in `unresolved_count`.\n\nThis is limited to 120 requests per minute, shared with [List workspace groups](/api-reference/list-workspace-groups). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner, admin, or editor of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused even though this endpoint only reads, and workspace API keys aren't accepted.</Note>","operationId":"list_group_members_api_workspace_groups__group_id__members_get","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the group whose members to list. Get it from `id` in [List workspace groups](/api-reference/list-workspace-groups).","title":"Group Id"},"description":"ID of the group whose members to list. Get it from `id` in [List workspace groups](/api-reference/list-workspace-groups).","example":"68b4e1d2c9a7f3001e2d4b61"},{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":254},{"type":"null"}],"description":"Only return members whose email or name contains this text, ignoring case. Up to 254 characters.","title":"Search"},"description":"Only return members whose email or name contains this text, ignoring case. Up to 254 characters.","example":"dana"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of members to skip. Defaults to 0.","default":0,"title":"Offset"},"description":"Number of members to skip. Defaults to 0.","example":0},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Number of members to return, from 1 to 200. Defaults to 50.","default":50,"title":"Limit"},"description":"Number of members to return, from 1 to 200. Defaults to 50.","example":50}],"responses":{"200":{"description":"One page of the group's members.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupMembersPage"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner, admin, or editor of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"404":{"description":"Group not found in this workspace."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Add group members","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nAdds members to a custom group, up to 200 per request. Their workspace role updates right away.\n\nOnly active members of the workspace can join a group. Pending invitees, guests, and the workspace owner can't. To add someone who isn't a member yet, invite them with [Invite workspace member](/api-reference/invite-workspace-member) and pass `group_id`. If any address can't be added, nobody is added.\n\nAdding someone who's already in the group changes nothing, so retrying is safe. A group whose `source` is `idp` gets its name and members from your identity provider, so they can't be changed here. You can't make a change that removes your own admin access.\n\nGroups need the Business plan or higher. Below it, this returns a `402`.\n\nThis is limited to 60 requests per minute, shared with the other endpoints that change groups or their members. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"add_group_members_api_workspace_groups__group_id__members_post","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the group to add members to. Get it from `id` in [List workspace groups](/api-reference/list-workspace-groups).","title":"Group Id"},"description":"ID of the group to add members to. Get it from `id` in [List workspace groups](/api-reference/list-workspace-groups).","example":"68b4e1d2c9a7f3001e2d4b61"},{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupMembersModifyRequest"}}}},"responses":{"200":{"description":"The members are in the group.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeResult"},"example":{"status":"success","message":"Members added to group."}}}},"400":{"description":"An address isn't an active workspace member, the group's members come from your identity provider, or the change would remove your own admin access."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The workspace's plan doesn't include groups."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"404":{"description":"Group not found in this workspace."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/groups/{group_id}/members/remove":{"post":{"summary":"Remove group members","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRemoves members from a custom group, up to 200 per request.\n\nEach affected member's role is worked out again from their other groups. A member left in no group becomes a viewer, or gets the workspace's SSO default role when SSO is set up.\n\nRemoving someone who isn't in the group changes nothing, so retrying is safe. A group whose `source` is `idp` gets its name and members from your identity provider, so they can't be changed here. You can't make a change that removes your own admin access. Removing members works on any plan, so you can take groups apart after a downgrade.\n\nThis is limited to 60 requests per minute, shared with the other endpoints that change groups or their members. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token for that workspace sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"remove_group_members_api_workspace_groups__group_id__members_remove_post","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the group to remove members from. Get it from `id` in [List workspace groups](/api-reference/list-workspace-groups).","title":"Group Id"},"description":"ID of the group to remove members from. Get it from `id` in [List workspace groups](/api-reference/list-workspace-groups).","example":"68b4e1d2c9a7f3001e2d4b61"},{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","title":"Workspaceid"},"description":"ID of the workspace. With a personal access token, use the token's workspace. Get it from `organization_id` in [Get app](/api-reference/get-app).","example":"67e0b12c4d8a3f005b21c9e4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupMembersModifyRequest"}}}},"responses":{"200":{"description":"The members are no longer in the group.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeResult"},"example":{"status":"success","message":"Members removed from group."}}}},"400":{"description":"The group's members come from your identity provider, or the change would remove your own admin access."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace, your token is for a different workspace or is read-only, or your credential can't be used on this endpoint."},"404":{"description":"Group not found in this workspace."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/group-shares":{"get":{"summary":"List group shares","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 workspace groups the app is shared with.\n\nEvery member of a shared group can use the app. A member's role in the app is the highest of the role you gave them directly and the roles of all the groups they're in that the app is shared with. Change a group's role with [Update group share](/api-reference/update-group-share), or stop sharing with [Remove group share](/api-reference/remove-group-share).\n\nThe response lists every share at once, with no paging. You need editor access to the app, and an editor or admin role in the app's workspace.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"list_shares_api_apps__app_id__group_shares_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":"The app's group shares.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareListResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you aren't an editor or admin in the app's workspace, or your API key is read-only."},"404":{"description":"App not found."}}},"post":{"summary":"Share app with groups","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nShares the app with one or more workspace groups, giving every member of each group access to the app.\n\nAll the groups get the same `role`. A member's role in the app is the highest of the role you gave them directly and the roles of all the groups they're in that the app is shared with. Members usually get access right away, and always by their next sign-in. Members your identity provider synced who don't have a Base44 account yet get access once they have one. Each member who gets access is also sent an invitation email, at most once per app.\n\nEach group succeeds or fails on its own, so read `results` rather than treating a successful response as every group shared. Sending a group the app is already shared with fails that group with `already_shared` and changes nothing, so retrying is safe. Change a share's role afterwards with [Update group share](/api-reference/update-group-share).\n\nYou need editor access to the app, and an editor or admin role in the app's workspace. Your workspace's plan must include workspace groups.\n\nThis is limited to 30 requests per minute. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"create_shares_api_apps__app_id__group_shares_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/CreateSharesRequest"}}}},"responses":{"200":{"description":"One result per group.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSharesResponse"}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"Your workspace's plan doesn't include workspace groups."},"403":{"description":"You don't have editor access to this app, you aren't an editor or admin in the app's workspace, or your API key is read-only."},"404":{"description":"App not found."},"422":{"description":"`group_ids` is missing, empty, or has more than 50 IDs, or `role` isn't one of the roles defined on the app's User entity."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute. Wait the number of seconds in the `Retry-After` header before you retry."}}}},"/api/apps/{app_id}/group-shares/{share_id}":{"put":{"summary":"Update group share","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 app role a group share gives the group's members.\n\nSend the whole new value. Leaving `role` out, or sending `null`, stops assigning a role, so members get the default `user` role. Lowering a group's role doesn't lower a member who has a higher role directly or through another shared group. Members' access usually updates right away, and always by their next sign-in. Sending the role the share already has changes nothing.\n\nYou need editor access to the app, and an editor or admin role in the app's workspace. Your workspace's plan must include workspace groups.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"update_share_api_apps__app_id__group_shares__share_id__put","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":"share_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the share, as `id` in the response of [List group shares](/api-reference/list-group-shares).","title":"Share Id"},"description":"ID of the share, as `id` in the response of [List group shares](/api-reference/list-group-shares).","example":"68d1c2e4a9b7f3001e5c8a41"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateShareRequest"}}}},"responses":{"200":{"description":"The share with its new role.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateShareResponse"}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"Your workspace's plan doesn't include workspace groups."},"403":{"description":"You don't have editor access to this app, you aren't an editor or admin in the app's workspace, or your API key is read-only."},"404":{"description":"App or share not found."},"422":{"description":"`role` isn't one of the roles defined on the app's User entity."}}},"delete":{"summary":"Remove group share","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nStops sharing the app with a group.\n\nMembers who can use the app only through this group lose access. Members with a direct role, or another shared group, keep access at the highest role they still have. If the call fails, the share is kept, so you can safely retry.\n\nThis works on any plan, so you can remove shares after your workspace's plan no longer includes groups. You need editor access to the app, and an editor or admin role in the app's workspace.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"delete_share_api_apps__app_id__group_shares__share_id__delete","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":"share_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the share, as `id` in the response of [List group shares](/api-reference/list-group-shares).","title":"Share Id"},"description":"ID of the share, as `id` in the response of [List group shares](/api-reference/list-group-shares).","example":"68d1c2e4a9b7f3001e5c8a41"}],"responses":{"200":{"description":"The share was removed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__app__user_apps__app_group_shares__api__OperationResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you aren't an editor or admin in the app's workspace, or your API key is read-only."},"404":{"description":"App or share not found."}}}},"/api/workspace/skills":{"get":{"summary":"List workspace skills","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 skill in a workspace, newest first, without their instructions. The list isn't paginated. A workspace has at most 100 skills. Get a skill's instructions with [Get workspace skill](/api-reference/get-workspace-skill).\n\nSkills belong to the workspace your credential is for. With a personal access token, that's the token's workspace, and there's no parameter to choose another one.\n\nThe AI builder sees the name and description of every enabled skill in the workspace, and loads a skill's instructions when its description fits the task.\n\nSkills need the Builder plan or higher. Below it, this returns a `403`.\n\nThis is limited to 120 requests per minute, shared with [Get workspace skill](/api-reference/get-workspace-skill). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as any member of the workspace, with a personal access token sent as a Bearer token, or from a signed-in session. A read-only token is refused even though this endpoint only reads, and workspace API keys aren't accepted.</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_workspace_skills_api_workspace_skills_get","responses":{"200":{"description":"The workspace's skills.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceSkillList"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The workspace's plan doesn't include skills, your token is read-only, or your credential can't be used on this endpoint."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}},"post":{"summary":"Create workspace skill","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 skill to a workspace. The new skill is enabled, so the AI builder can use it right away.\n\nThe AI builder sees the name and description of every enabled skill in the workspace, and loads a skill's instructions when its description fits the task.\n\nSkills belong to the workspace your credential is for. With a personal access token, that's the token's workspace, and there's no parameter to choose another one.\n\nSkill names are unique within a workspace, so retrying a create that succeeded returns a `409`. A workspace has at most 100 skills, and creating another returns a `400`.\n\nSkills need the Builder plan or higher. Below it, this returns a `403`.\n\nThis is limited to 60 requests per minute, shared with the other endpoints that change skills. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</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":"create_workspace_skill_api_workspace_skills_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSkillRequest"}}},"required":true},"responses":{"200":{"description":"The new skill.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceSkill"}}}},"400":{"description":"The workspace already has 100 skills."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The workspace's plan doesn't include skills, you aren't an owner or admin of the workspace, your token is read-only, or your credential can't be used on this endpoint."},"409":{"description":"Another skill in the workspace already has this name, or your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/skills/{skill_id}":{"get":{"summary":"Get workspace skill","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 skill with its instructions.\n\nSkills belong to the workspace your credential is for. With a personal access token, that's the token's workspace, and there's no parameter to choose another one.\n\nSkills need the Builder plan or higher. Below it, this returns a `403`.\n\nThis is limited to 120 requests per minute, shared with [List workspace skills](/api-reference/list-workspace-skills). A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as any member of the workspace, with a personal access token sent as a Bearer token, or from a signed-in session. A read-only token is refused even though this endpoint only reads, and workspace API keys aren't accepted.</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_skill_api_workspace_skills__skill_id__get","parameters":[{"name":"skill_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the skill to get. Get it from `id` in [List workspace skills](/api-reference/list-workspace-skills).","title":"Skill Id"},"description":"ID of the skill to get. Get it from `id` in [List workspace skills](/api-reference/list-workspace-skills).","example":"68d2a1f4c9e7b3001f2a5c88"}],"responses":{"200":{"description":"The skill.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceSkill"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The workspace's plan doesn't include skills, your token is read-only, or your credential can't be used on this endpoint."},"404":{"description":"Skill not found in this workspace."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}},"put":{"summary":"Update workspace skill","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 skill's name, description, or instructions, or turns it on or off. Send only the fields you want to change. A field you leave out, or send as `null`, keeps its value, and an empty body changes nothing.\n\nSet `enabled` to `false` to stop the AI builder from using the skill without deleting it.\n\nSkills belong to the workspace your credential is for. With a personal access token, that's the token's workspace, and there's no parameter to choose another one.\n\nSkills need the Builder plan or higher. Below it, this returns a `403`.\n\nThis is limited to 60 requests per minute, shared with the other endpoints that change skills. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</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":"update_workspace_skill_api_workspace_skills__skill_id__put","parameters":[{"name":"skill_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the skill to update. Get it from `id` in [List workspace skills](/api-reference/list-workspace-skills).","title":"Skill Id"},"description":"ID of the skill to update. Get it from `id` in [List workspace skills](/api-reference/list-workspace-skills).","example":"68d2a1f4c9e7b3001f2a5c88"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSkillRequest"}}}},"responses":{"200":{"description":"The updated skill.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceSkill"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The workspace's plan doesn't include skills, you aren't an owner or admin of the workspace, your token is read-only, or your credential can't be used on this endpoint."},"404":{"description":"Skill not found in this workspace."},"409":{"description":"Another skill in the workspace already has this name, or your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete workspace skill","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 skill. This can't be undone, and the AI builder stops using the skill right away. To keep it but stop it being used, set `enabled` to `false` with [Update workspace skill](/api-reference/update-workspace-skill) instead.\n\nSkills belong to the workspace your credential is for. With a personal access token, that's the token's workspace, and there's no parameter to choose another one.\n\nSkills need the Builder plan or higher. Below it, this returns a `403`.\n\nThis is limited to 60 requests per minute, shared with the other endpoints that change skills. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"delete_workspace_skill_api_workspace_skills__skill_id__delete","parameters":[{"name":"skill_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the skill to delete. Get it from `id` in [List workspace skills](/api-reference/list-workspace-skills).","title":"Skill Id"},"description":"ID of the skill to delete. Get it from `id` in [List workspace skills](/api-reference/list-workspace-skills).","example":"68d2a1f4c9e7b3001f2a5c88"}],"responses":{"200":{"description":"The skill was deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeResult"},"example":{"status":"success","message":"Skill deleted"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The workspace's plan doesn't include skills, you aren't an owner or admin of the workspace, your token is read-only, or your credential can't be used on this endpoint."},"404":{"description":"Skill not found in this workspace."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/session-recordings/apps/{app_id}/recording-settings":{"get":{"summary":"Get session recording settings","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns whether session recordings are on for the app, and how they capture visits. See [Session recordings](/documentation/performance-and-seo/session-recordings) for how recordings work and what they cost.\n\n`enabled` reads `false` when recordings were turned on but the app has since moved to another owner or workspace, or the owner's plan no longer includes them. A `true` value doesn't guarantee visits are being recorded: that also needs the app to be published, and recordings to have reached the app owner's account.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"get_recording_settings_api_session_recordings_apps__app_id__recording_settings_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","maxLength":64,"pattern":"^[a-zA-Z0-9._-]+$","description":"ID of the app.","title":"App Id"},"description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The app's session recording settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordingSettingsResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"Your API key is read-only."},"404":{"description":"App not found, you don't have editor access to it, or it's outside the apps your API key can reach."},"422":{"description":"`app_id` isn't a valid app ID."}}},"patch":{"summary":"Update session recording capture settings","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 session recordings capture visits: whether the app's owners and admins are recorded, and what share of visits gets recorded. Send one field or both. A field you leave out keeps its value.\n\nThe app needs recording settings first, which the first call to [Turn session recordings on or off](/api-reference/turn-session-recordings-on-or-off) creates, whether it turns them on or off. After that, you can change these settings at any time.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"update_recording_capture_settings_api_session_recordings_apps__app_id__recording_settings_patch","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","maxLength":64,"pattern":"^[a-zA-Z0-9._-]+$","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/RecordingCaptureSettingsUpdate"}}}},"responses":{"200":{"description":"The app's updated session recording settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordingSettingsResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"Your API key is read-only."},"404":{"description":"App not found, you don't have editor access to it, or it's outside the apps your API key can reach."},"409":{"description":"Recordings have never been turned on or off for this app."},"422":{"description":"The body has neither field, an unknown field, or a `sampling_rate` outside `0` to `1` or not a multiple of `0.05`."}}},"post":{"summary":"Turn session recordings on or off","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nTurns session recordings on or off for the app. Recordings are available on the Builder plan and above, and capture starts once the app is published. See [Session recordings](/documentation/performance-and-seo/session-recordings).\n\nThe first time you turn recordings on starts the app's rolling 30-day recording window. Turning them off and on again doesn't restart it.\n\nThe response echoes the `enabled` value you sent. When you turn recordings off, `enabled_at` and `enabled_by` keep the values from the last time they were turned on, including after the app has moved to another owner. [Get session recording settings](/api-reference/get-session-recording-settings) reports `null` for both.\n\n<Warning>Recording visitors may require their consent in some regions, such as the EU, the UK, and California. Base44 doesn't add a consent banner to your app, so make sure you meet the privacy laws that apply to your visitors before you turn recordings on.</Warning>\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"update_recording_settings_api_session_recordings_apps__app_id__recording_settings_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","maxLength":64,"pattern":"^[a-zA-Z0-9._-]+$","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/RecordingSettingsUpdate"}}}},"responses":{"200":{"description":"The app's session recording settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordingSettingsResponse"}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"You're turning recordings on, and your plan doesn't include them."},"403":{"description":"Your API key is read-only."},"404":{"description":"App not found, you don't have editor access to it, or it's outside the apps your API key can reach."},"409":{"description":"You're turning recordings on, and they aren't available for this app's owner yet."},"422":{"description":"`enabled` is missing or isn't a boolean."}}}},"/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\nSet `name`, `user_description`, `public_settings`, `custom_instructions`, `secrets`, and `prevent_iframe_embedding` in the same request to configure the app before its first build turn runs. Without a `name`, the app starts as `untitled`, and once a build turn changes it Base44 names and describes it, replacing any `user_description` you sent.\n\n<Warning>`secrets` are applied after the app is saved. A request that fails on a `secrets` entry still leaves the new app in your workspace, and it counts toward your plan's app limit. Delete it before you retry.</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":"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"]}}},"name":{"type":"string","description":"Display name of the app. Omit it to have Base44 name the app once a build turn changes it. The app's URL slug is built from the name.","example":"My CRM"},"user_description":{"type":"string","description":"Description of the app. When you omit `name`, Base44 replaces it with a generated description once a build turn changes the app.","example":"A CRM to track leads and deals"},"public_settings":{"type":"string","enum":["public_without_login","public_with_login","workspace_with_login","private_with_login"],"description":"Who can open the published app. `public_without_login` lets anyone in, unless the workspace enforces SSO for apps, in which case visitors must log in. `public_with_login` lets anyone in who logs in, `workspace_with_login` admits only logged-in members of the workspace, and `private_with_login` admits only users who were granted access. Omit it to use the workspace default. `workspace_with_login` and `private_with_login` need a paid plan, and a workspace policy can restrict which values you can choose. Like other publish settings, it takes effect on the live app once you [deploy the app](/api-reference/deploy-an-app).","example":"public_with_login"},"custom_instructions":{"type":"string","description":"Instructions the builder follows on every turn, starting with the first build. Only the first 10,000 characters reach the builder.","example":"Use a dark theme and keep every page mobile friendly."},"secrets":{"type":"object","description":"Secrets to set on the app before its first build turn, keyed by secret name. Each entry sets the secret to `value`. The name must not be empty or contain `=`.","additionalProperties":{"type":"object","required":["type","value"],"properties":{"type":{"type":"string","enum":["value"],"description":"Always `value`.","example":"value"},"value":{"type":"string","description":"The secret's value.","example":"sk_test_example"}}},"example":{"STRIPE_API_KEY":{"type":"value","value":"sk_test_example"}}},"prevent_iframe_embedding":{"type":"boolean","default":true,"description":"Whether the published app refuses to load inside an iframe on another site. Defaults to `true`. Set it to `false` to embed the app, although a workspace embedding policy can still restrict where. Preview URLs are not affected. It takes effect on the live app once you deploy it.","example":false},"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/UserApp"}}}},"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, your role is not editor-capable, your access token is scoped to a different workspace, or your plan or workspace policy doesn't allow the `public_settings` value you sent."},"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/UserApp"},"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},{"name":"folder_id","in":"query","required":false,"description":"Only return apps in this folder. Get folder IDs from [List app folders](/api-reference/list-app-folders). You still only get apps you can access, and a folder you can't see returns an empty list. For an agent folder, also pass `app_type=user_agent`.","schema":{"type":"string"},"example":"68c1f2a9b4e7d3005a2c9e11"},{"name":"exclude_foldered","in":"query","required":false,"description":"Set to `true` to leave out apps that are in a folder you can see. Ignored when `folder_id` is set.","schema":{"type":"string","enum":["true"]},"example":"true"},{"name":"include_folder_ids","in":"query","required":false,"description":"Set to `true` to add `folder_ids` to each app: the IDs of the folders you can see that hold it. For agents, also pass `app_type=user_agent`, or agent folders are left out.","schema":{"type":"string","enum":["true"]},"example":"true"}]}},"/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/UserApp"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or it doesn't exist."}}},"delete":{"summary":"Delete app","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 specified app to the trash. The live app goes offline right away and stops appearing in [List apps](/api-reference/list-apps).\n\nYou can delete an app you own. A workspace admin can delete any app in the workspace, and the owner is notified when they do. An editor who doesn't own the app can't delete it.\n\nThe app stays in the trash for 30 days. Restore it within that window with [Restore deleted app](/api-reference/restore-deleted-app), or from Recently deleted in the Base44 dashboard.\n\n<Warning>Restoring the app doesn't reconnect the resources this delete releases, such as its custom domains and OAuth connections. Read `deleted_resources` in the response to see what to set up again.</Warning>\n\nPause the app's Google Ads campaigns before you delete it. While one is still serving, the delete is rejected rather than left spending on an app that's gone.\n\nThe whole teardown runs before you get a response, so the call takes longer on an app with many connected resources. Retrying is safe. Deleting an app that's already in the trash returns that app and changes nothing.\n\nA delete that fails partway leaves the app live, so check the app before you retry. Some of its connected resources can already be released by then.\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_delete_api_apps__app_id__delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to delete.","title":"App Id"},"description":"ID of the app to delete.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The deleted app.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedApp"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You can't delete this app, because you don't have access to it, it doesn't exist, or you neither own it nor administer its workspace. A read-only personal access token is refused here too."},"404":{"description":"The app is outside the workspace your credential is scoped to, or outside the apps that credential is allowed to reach."},"409":{"description":"The app still has running ads campaigns to stop first, or it's busy with another operation."}}},"put":{"summary":"Update app","operationId":"api_put_api_apps__item_id__put","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"title":"App Id"},"description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"UpdateApp","type":"object","properties":{"name":{"type":"string","description":"New name for the app.","example":"Nordwind Furniture"},"user_description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"New description for the app, or `null` to remove it.","example":"An online catalog and order tracker for a furniture workshop."},"public_settings":{"type":"string","enum":["public_without_login","public_with_login","workspace_with_login","private_with_login"],"description":"Who can open the published app. `public_without_login` lets anyone in, unless the workspace enforces SSO for apps, in which case visitors must log in. `public_with_login` lets anyone in who logs in, `workspace_with_login` admits only logged-in members of the workspace, and `private_with_login` admits only users who were granted access. Switching to `workspace_with_login` or `private_with_login` needs a paid plan, and a workspace policy can restrict which values you can choose. An agent app always stays `private_with_login`.","example":"public_with_login"},"auth_config":{"type":"object","description":"Which login methods the app's login page offers. Send only the methods you want to change. If the workspace enforces SSO for apps, the login page offers only SSO, whatever these are set to. Set up the app's own SSO provider with [Update app SSO settings](/api-reference/update-app-sso-settings).","properties":{"enable_username_password":{"type":"boolean","description":"`true` lets people sign in with an email and password, and `false` removes that option.","example":true},"enable_google_login":{"type":"boolean","description":"`true` lets people sign in with a Google account, and `false` removes that option.","example":true},"enable_microsoft_login":{"type":"boolean","description":"`true` lets people sign in with a Microsoft account, and `false` removes that option.","example":true},"enable_facebook_login":{"type":"boolean","description":"`true` lets people sign in with a Facebook account, and `false` removes that option.","example":true},"enable_apple_login":{"type":"boolean","description":"`true` lets people sign in with an Apple account, and `false` removes that option.","example":true}},"example":{"enable_username_password":true,"enable_google_login":false}},"is_remixable":{"type":"boolean","description":"`true` shows the Base44 badge on the published app and lets others remix it. The badge only shows while the app is `public_with_login` or `public_without_login`. `false` hides the badge and turns remixing off, which needs a paid plan on the app's workspace.","example":false},"hide_entity_created_by":{"type":"boolean","description":"`true` leaves the email of the user who created each record (`created_by`) out of the app's entity records, and `false` includes it. `created_by_id` is always included. Newer apps always leave the email out and ignore this field.","example":true},"dev_environment_enabled":{"type":"boolean","description":"`true` turns on test data. The app gets a test database next to its live one, so you can try changes in the preview without touching live data. Turning it on needs a Builder plan or higher on the app's workspace. `false` turns it off.","example":true}}},"example":{"name":"Nordwind Furniture","public_settings":"public_with_login"}}}},"responses":{"200":{"description":"The app, with its new details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserApp"}}}},"400":{"description":"The app is an agent, and `public_settings` isn't `private_with_login`."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The app's workspace plan doesn't include the `public_settings` value you sent, or you're turning on test data and the plan doesn't include it."},"403":{"description":"You don't have access to this app, it doesn't exist, or your API key is read-only. Also returned when the workspace policy doesn't allow the `public_settings` value you sent, or when you set `is_remixable` to `false` without a paid plan."},"422":{"description":"A field has the wrong type, or `public_settings` isn't one of its allowed values."}},"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 app's details and settings. That covers its name and description, who can open it, how people sign in, and a few display settings.\n\nSend only the fields you want to change. A field you leave out keeps its value, and so does a login method you leave out of `auth_config`.\n\nRenaming doesn't change the app's address. Change that with [Change app slug](/api-reference/change-app-slug).\n\nA new `public_settings` value applies to the published app right away, and so does whether others can remix the app. Changes to `auth_config`, the Base44 badge, and `hide_entity_created_by` take effect on the published app once you [deploy the app](/api-reference/deploy-an-app), or sooner if a `public_settings` change carries them along.\n\n<Warning>This endpoint also accepts other app fields, but only the fields documented here are part of this API. Don't send anything else. Other fields aren't covered by the contract and can change or be rejected without notice.</Warning>"}},"/api/app-catalog-items/catalog/{item_id}/archive":{"post":{"summary":"Archive template listing","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 template listing made from an app, which takes it off the template gallery.\n\nThe listing stops showing in the gallery right away. Apps that people already made from the template aren't affected. The app itself isn't changed either, and once its listing is archived you can list it as a template again from the builder. Archiving a listing that's already archived succeeds and changes nothing.\n\nYou need to be an owner or admin of the app's workspace. For a listing shared only within a workspace, you need to be an owner or admin of that workspace.\n\nArchiving a listing is limited to 30 requests a minute per caller.\n\n<Note>This endpoint accepts a personal API key. A read-only key is refused.</Note>","operationId":"archive_item_api_app_catalog_items_catalog__item_id__archive_post","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the template listing to archive. Get it from `catalog_items` in [Check app delete impact](/api-reference/check-app-delete-impact).","title":"Item Id"},"description":"ID of the template listing to archive. Get it from `catalog_items` in [Check app delete impact](/api-reference/check-app-delete-impact).","example":"68b2f0c1a4d9e7001c3b5a22"}],"responses":{"200":{"description":"The listing is archived.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArchivedTemplateListing"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace the listing belongs to, you don't have access to the app it was made from, or your API key is read-only."},"404":{"description":"The listing doesn't exist, or the app it was made from is outside the workspace or apps your API key is scoped to."},"429":{"description":"Rate limit reached. Retry later."}}}},"/api/app-catalog-items/catalog/{item_id}/submit-update":{"post":{"summary":"Submit template listing update","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSends the app's current state, and optionally new listing details, as an update to its approved template listing. It's the **Submit update** button in the app's template settings.\n\nThe update copies the app as it is in the editor, including changes you haven't published. Publishing with [Deploy an app](/api-reference/deploy-an-app) doesn't change what goes in.\n\nWhat happens next depends on the listing:\n\n- A workspace template updates right away.\n- A public template goes to Base44 for review. The current version stays live until the update is approved. Submitting again replaces an update that's still waiting, including its listing details.\n\nOnly an approved listing that isn't archived can be updated. Get it from `catalog_items` in [Check app delete impact](/api-reference/check-app-delete-impact).\n\nSubmitting an update is limited to 10 requests a minute per caller.\n\n<Note>This endpoint accepts a personal API key. A read-only key is refused.</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":"submit_update_api_app_catalog_items_catalog__item_id__submit_update_post","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the template listing to update. Get it from `catalog_items` in [Check app delete impact](/api-reference/check-app-delete-impact).","title":"Item Id"},"description":"ID of the template listing to update. Get it from `catalog_items` in [Check app delete impact](/api-reference/check-app-delete-impact).","example":"68b2f0c1a4d9e7001c3b5a22"}],"requestBody":{"content":{"application/json":{"schema":{"title":"SubmitTemplateListingUpdate","type":"object","properties":{"metadata":{"type":"object","description":"Listing details to change with this update. Send only the fields you want to change, and only the fields documented here, as other keys aren't supported.","properties":{"name":{"type":"string","description":"Name of the listing.","example":"CRM starter"},"description":{"type":"string","description":"Short description shown in the gallery.","example":"Track leads and deals."},"detailed_description":{"type":"string","description":"Full description of the template.","example":"A starter CRM with pipelines and reminders."},"screenshot_urls":{"type":"array","items":{"type":"string"},"description":"Screenshot URLs, up to 20. Each must be the public link of an image you uploaded with [Upload app file](/api-reference/upload-app-file). Links hosted anywhere else are refused.","example":["https://storage.base44.com/6820f3a4e7b91d003c45a1f2/3f2504e0_screenshot.png"]},"categories":{"type":"array","items":{"type":"string","enum":["Marketing & Sales","Operations","Data & Analytics","Content Generation","HR & Legal","Finance","Education","Community","Lifestyle & Hobbies","Games & Entertainment"]},"description":"Gallery categories.","example":["Operations"]},"version":{"type":"string","description":"Version label.","example":"1.1.0"},"changelog":{"type":"string","description":"What changed in this version.","example":"Adds a reports page."},"is_paid":{"type":"boolean","description":"Whether the template is sold. A workspace template can't be paid.","example":false},"price_usd":{"type":"number","description":"Price in USD for a paid template, from 0.99 to 1000 with at most two decimals.","example":19.99},"demo_video_url":{"type":"string","description":"Link to a demo video.","example":"https://example.com/demo.mp4"},"support_email":{"type":"string","description":"Support contact.","example":"support@example.com"},"documentation_url":{"type":"string","description":"Link to documentation.","example":"https://example.com/docs"}},"example":{"version":"1.1.0","changelog":"Adds a reports page."}}}},"example":{"metadata":{"version":"1.1.0","changelog":"Adds a reports page."}}}},"required":false},"responses":{"200":{"description":"The update was submitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmittedTemplateListingUpdate"}}}},"400":{"description":"The listing is archived or isn't approved, a `metadata` value is invalid, a workspace template was set to paid, or new paid listings aren't allowed."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace the listing belongs to, you don't have access to the app it was made from, its workspace restricts public template publishing (`public_template_publishing_not_allowed`), you can't make paid listings in the workspace, or your personal access token is read-only."},"404":{"description":"The listing doesn't exist, or the app it was made from is outside the workspace or apps your API key is scoped to."},"429":{"description":"Rate limit reached. Retry later."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/app-catalog-items/catalog/{item_id}/pending-changes":{"get":{"summary":"Get template listing pending update","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nShows whether an update to a template listing is waiting for review, and which listing details it changes. Check it after you [submit an update](/api-reference/submit-template-listing-update) to a public template. A workspace template never has one waiting, because its updates apply right away.\n\nGet it from `catalog_items` in [Check app delete impact](/api-reference/check-app-delete-impact).\n\nYou need to be an owner or admin of the app's workspace. For a listing shared only within a workspace, you need to be an owner or admin of that workspace.\n\nThis endpoint is limited to 60 requests a minute per caller.\n\n<Note>This endpoint accepts a personal API key. A read-only key is refused, even though this endpoint only reads.</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_pending_changes_api_app_catalog_items_catalog__item_id__pending_changes_get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the template listing. Get it from `catalog_items` in [Check app delete impact](/api-reference/check-app-delete-impact).","title":"Item Id"},"description":"ID of the template listing. Get it from `catalog_items` in [Check app delete impact](/api-reference/check-app-delete-impact).","example":"68b2f0c1a4d9e7001c3b5a22"}],"responses":{"200":{"description":"What's waiting for review.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateListingPendingChanges"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace the listing belongs to, you don't have access to the app it was made from, or your API key is read-only."},"404":{"description":"The listing doesn't exist, or the app it was made from is outside the workspace or apps your API key is scoped to."},"429":{"description":"Rate limit reached. Retry later."}}}},"/api/app-folders/{folder_id}/items":{"post":{"summary":"Add apps to folder","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nPuts apps in a folder. An app is in one folder at a time, so this moves an app that's already in another folder.\n\nThe apps must be in the workspace, not deleted, and of the folder's kind: builder apps for a `user_app` folder and agents for a `user_agent` one. You can add any app in the workspace, including one you can't open. If an app is in a folder you can't change, such as another user's personal folder, the request is refused. Send up to 500 IDs per request. IDs that aren't valid app IDs are ignored and aren't counted in the response.\n\nYou can change your own personal folders. Workspace folders need the editor role or higher, and any editor can change any workspace folder. A folder that doesn't exist, or another user's personal folder, returns a `404` with the code `folder_not_found`. A personal access token limited to selected apps can only use the apps it lists. Any other app ID returns a `404`.\n\nThis is limited to 60 requests per minute, shared with the other endpoints that change folders. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this with a personal access token sent as a Bearer token, or from a signed-in session. A token works on its own workspace's folders. Workspace API keys aren't accepted, and a read-only token is refused.</Note>","operationId":"api_add_items_api_app_folders__folder_id__items_post","parameters":[{"name":"folder_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the folder to add apps to. Get it from `id` in [List app folders](/api-reference/list-app-folders).","title":"Folder Id"},"description":"ID of the folder to add apps to. Get it from `id` in [List app folders](/api-reference/list-app-folders).","example":"68c1f2a9b4e7d3005a2c9e11"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddFolderAppsRequest"}}}},"responses":{"200":{"description":"The apps are in the folder.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddFolderAppsResult"}}}},"400":{"description":"`app_ids` lists more than 500 IDs or holds no valid app ID (`invalid_request` for more than 500), or an app isn't of the folder's kind."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The folder is a workspace folder and you're a viewer or guest, or your token is read-only. Also returned when an app isn't in the workspace or is deleted, or is in a folder you can't change."},"404":{"description":"`folder_not_found`: the folder doesn't exist in the workspace, or it's another user's personal folder. Also returned when your personal access token is limited to selected apps and doesn't list the app."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/app-folders":{"post":{"summary":"Create app folder","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 folder for builder apps or agents.\n\nYou can change your own personal folders. Workspace folders need the editor role or higher, and any editor can change any workspace folder. A workspace holds up to 500 folders of both kinds, counting every member's personal folders, and a folder can have at most 20 folders above it. Folder names don't have to be unique, so retrying a create that timed out can leave you with two folders.\n\nThis is limited to 60 requests per minute, shared with the other endpoints that change folders. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this with a personal access token sent as a Bearer token, or from a signed-in session. A token works on its own workspace's folders. Workspace API keys aren't accepted, and a read-only token is refused.</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":"api_create_api_app_folders_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAppFolderRequest"}}},"required":true},"responses":{"200":{"description":"The new folder.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppFolderSummary"}}}},"400":{"description":"`app_type` isn't `user_app` or `user_agent`, the parent has a different `scope` or `app_type` or is another user's personal folder, the folder would have more than 20 folders above it, or the workspace already has 500 folders."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"`scope` is `workspace` and you're a viewer or guest, or your token is read-only."},"404":{"description":"The parent folder doesn't exist."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/app-folders/{item_id}":{"delete":{"summary":"Delete app folder","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 folder. The apps in it stay in the workspace and are only taken out of the folder.\n\nSet `mode` to decide what happens to the folders inside it: `reparent`, the default, moves them up into the deleted folder's parent, and `cascade` deletes them along with it, taking their apps out of folders too.\n\nYou can change your own personal folders. Workspace folders need the editor role or higher, and any editor can change any workspace folder. A folder that doesn't exist, or another user's personal folder, returns a `404` with the code `folder_not_found`.\n\nThis is limited to 60 requests per minute, shared with the other endpoints that change folders. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this with a personal access token sent as a Bearer token, or from a signed-in session. A token works on its own workspace's folders. Workspace API keys aren't accepted, and a read-only token is refused.</Note>","operationId":"api_delete_api_app_folders__item_id__delete","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the folder to delete. Get it from `id` in [List app folders](/api-reference/list-app-folders).","title":"Item Id"},"description":"ID of the folder to delete. Get it from `id` in [List app folders](/api-reference/list-app-folders).","example":"68c1f2a9b4e7d3005a2c9e11"},{"name":"mode","in":"query","required":false,"schema":{"type":"string","description":"What happens to the folders inside it. `reparent`, the default, moves them up into the deleted folder's parent. `cascade` deletes them too.","enum":["reparent","cascade"],"default":"reparent","title":"Mode"},"description":"What happens to the folders inside it. `reparent`, the default, moves them up into the deleted folder's parent. `cascade` deletes them too.","example":"reparent"}],"responses":{"200":{"description":"The folder was deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAppFolderResult"}}}},"400":{"description":"`mode` isn't `reparent` or `cascade`."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The folder is a workspace folder and you're a viewer or guest, or your token is read-only."},"404":{"description":"`folder_not_found`: the folder doesn't exist in the workspace, or it's another user's personal folder."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"summary":"Update app folder","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRenames a folder, or changes its color, icon or position. Send only the fields you want to change.\n\nA folder's `scope`, `app_type`, `owner_id` and `parent_folder_id` can't change here, and sending any of them returns a `400`.\n\nYou can change your own personal folders. Workspace folders need the editor role or higher, and any editor can change any workspace folder. A folder that doesn't exist, or another user's personal folder, returns a `404` with the code `folder_not_found`.\n\nThis is limited to 60 requests per minute, shared with the other endpoints that change folders. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this with a personal access token sent as a Bearer token, or from a signed-in session. A token works on its own workspace's folders. Workspace API keys aren't accepted, and a read-only token is refused.</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":"api_put_api_app_folders__item_id__put","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the folder to update. Get it from `id` in [List app folders](/api-reference/list-app-folders).","title":"Item Id"},"description":"ID of the folder to update. Get it from `id` in [List app folders](/api-reference/list-app-folders).","example":"68c1f2a9b4e7d3005a2c9e11"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAppFolderRequest"}}}},"responses":{"200":{"description":"The updated folder.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppFolderSummary"}}}},"400":{"description":"The request sends `scope`, `app_type`, `owner_id`, `organization_id` or `parent_folder_id`."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The folder is a workspace folder and you're a viewer or guest, or your token is read-only."},"404":{"description":"`folder_not_found`: the folder doesn't exist in the workspace, or it's another user's personal folder."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/app-folders/{folder_id}/items/{app_id}":{"delete":{"summary":"Remove app from folder","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nTakes an app out of a folder. The app stays in the workspace. Removing an app that isn't in the folder succeeds and changes nothing.\n\nYou can change your own personal folders. Workspace folders need the editor role or higher, and any editor can change any workspace folder. A folder that doesn't exist, or another user's personal folder, returns a `404` with the code `folder_not_found`. A personal access token limited to selected apps can only use the apps it lists. Any other app ID returns a `404`.\n\nThis is limited to 600 requests per minute. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this with a personal access token sent as a Bearer token, or from a signed-in session. A token works on its own workspace's folders. Workspace API keys aren't accepted, and a read-only token is refused.</Note>","operationId":"api_remove_item_api_app_folders__folder_id__items__app_id__delete","parameters":[{"name":"folder_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the folder to remove the app from. Get it from `id` in [List app folders](/api-reference/list-app-folders).","title":"Folder Id"},"description":"ID of the folder to remove the app from. Get it from `id` in [List app folders](/api-reference/list-app-folders).","example":"68c1f2a9b4e7d3005a2c9e11"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to take out of the folder.","title":"App Id"},"description":"ID of the app to take out of the folder.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The app isn't in the folder anymore.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveFolderAppResult"}}}},"400":{"description":"`app_id` isn't a valid app ID."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The folder is a workspace folder and you're a viewer or guest, or your token is read-only."},"404":{"description":"`folder_not_found`: the folder doesn't exist in the workspace, or it's another user's personal folder. Also returned when your personal access token is limited to selected apps and doesn't list the app."},"429":{"description":"Rate limit exceeded."}}}},"/api/app-folders/tree":{"get":{"summary":"List app folders","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 folders you can see in the workspace: its workspace folders, which every member sees, and your personal folders, which only you see.\n\nA folder holds builder apps or agents, never both. Set `app_type` to `user_agent` to list agent folders.\n\nEach folder names the folder it sits in through `parent_folder_id`, so build the tree from that. This returns folders only. To list the apps in a folder, call [List apps](/api-reference/list-apps) with `folder_id`.\n\nThis is limited to 300 requests per minute. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit.\n\n<Note>Call this with a personal access token sent as a Bearer token, or from a signed-in session. A token works on its own workspace's folders. Workspace API keys aren't accepted, and a read-only token works.</Note>","operationId":"get_folder_tree_api_app_folders_tree_get","parameters":[{"name":"app_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Which folders to list: `user_app` for builder app folders, or `user_agent` for agent folders. Defaults to `user_app`, and so does any other value.","title":"App Type"},"description":"Which folders to list: `user_app` for builder app folders, or `user_agent` for agent folders. Defaults to `user_app`, and so does any other value.","example":"user_app"}],"responses":{"200":{"description":"The folders you can see.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppFolderTree"}}}},"401":{"description":"Missing or invalid credentials."},"429":{"description":"Rate limit exceeded."}}}},"/api/fonts":{"post":{"summary":"Upload workspace font","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nUploads a font file to the workspace. Send it as `multipart/form-data` in a field named `file`.\n\nWorkspace fonts are font files the workspace uploaded, which every app in the workspace can use. Each font is one weight and style of a family, so a family with a regular and a bold weight is two fonts. Upload one file per weight and style.\n\nThe file can be TrueType, OpenType, WOFF or WOFF2, up to 5 MB. Font collections (`.ttc`) aren't supported. Base44 reads the family name, weight and style from the file itself, not from its file name, and stores the font as WOFF2.\n\nWhen the workspace already has a font with the same family name, ignoring case, weight and style, you get that font back and the file you sent is discarded, so retrying an upload is safe below the font limit. A family name that's already a Google Fonts family is refused, and so is an upload once the workspace has 256 fonts.\n\nIn this response, `created_date` includes a `+00:00` offset for a newly added font.\n\nFonts belong to the workspace your credential is for. With a personal access token, that's the token's workspace, and there's no parameter to choose another one.\n\nUploads are limited to 30 a minute for each workspace.\n\n<Note>Call this as an owner, admin, or editor of the workspace, with a personal access token sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"upload_font_api_fonts_post","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/UploadWorkspaceFont"}}}},"responses":{"200":{"description":"The uploaded font, or the existing font with the same family, weight and style.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceFont"}}}},"400":{"description":"The file is over 5 MB, isn't a font Base44 can read, is a font collection, or its family name is empty, over 100 characters, or contains characters such as `/`, `,`, `_`, quotes or brackets. `detail` carries a code for the reason, such as `file_too_large`."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner, admin, or editor of the workspace, your token is read-only, or your credential can't be used on this endpoint."},"409":{"description":"Your workspace requires an unlocked SSO session."},"422":{"description":"`file` is missing."},"429":{"description":"Rate limit exceeded."}}},"get":{"summary":"List workspace fonts","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 workspace's fonts, newest first. The list isn't paginated. A workspace has at most 256 fonts.\n\nWorkspace fonts are font files the workspace uploaded, which every app in the workspace can use. Each font is one weight and style of a family, so a family with a regular and a bold weight is two fonts.\n\nFonts belong to the workspace your credential is for. With a personal access token, that's the token's workspace, and there's no parameter to choose another one.\n\nThis is limited to 120 requests per minute, shared with [Get workspace font](/api-reference/get-workspace-font). Some workspaces have a different limit.\n\n<Note>Call this as a member of the workspace, with a personal access token sent as a Bearer token, or from a signed-in session. A read-only token is refused even though this endpoint only reads, and workspace API keys aren't accepted.</Note>","operationId":"list_fonts_api_fonts_get","parameters":[{"name":"family_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Keep only the fonts in this family, matched exactly but ignoring case. A name longer than 100 characters matches nothing.","title":"Family Name"},"description":"Keep only the fonts in this family, matched exactly but ignoring case. A name longer than 100 characters matches nothing.","example":"Acme Sans"}],"responses":{"200":{"description":"The workspace's fonts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceFontList"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't a member of the workspace, your token is read-only, or your credential can't be used on this endpoint."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/api/fonts/{font_id}":{"get":{"summary":"Get workspace font","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 of the workspace's fonts.\n\nFonts belong to the workspace your credential is for. With a personal access token, that's the token's workspace, and there's no parameter to choose another one.\n\nThis is limited to 120 requests per minute, shared with [List workspace fonts](/api-reference/list-workspace-fonts). Some workspaces have a different limit.\n\n<Note>Call this as a member of the workspace, with a personal access token sent as a Bearer token, or from a signed-in session. A read-only token is refused even though this endpoint only reads, and workspace API keys aren't accepted.</Note>","operationId":"get_font_api_fonts__font_id__get","parameters":[{"name":"font_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the font to get. Get it from `id` in [List workspace fonts](/api-reference/list-workspace-fonts).","title":"Font Id"},"description":"ID of the font to get. Get it from `id` in [List workspace fonts](/api-reference/list-workspace-fonts).","example":"68d4b2e1f0a9c8001b3e5f27"}],"responses":{"200":{"description":"The font.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceFont"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't a member of the workspace, your token is read-only, or your credential can't be used on this endpoint."},"404":{"description":"No font with this ID in the workspace."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}},"delete":{"summary":"Delete workspace font","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 of the workspace's fonts. This can't be undone, and the font disappears from the font picker in every app. Apps that already added the font to their code keep their own copy of it.\n\nFonts belong to the workspace your credential is for. With a personal access token, that's the token's workspace, and there's no parameter to choose another one.\n\nThis is limited to 30 requests per minute. Some workspaces have a different limit.\n\n<Note>Call this as an owner or admin of the workspace, with a personal access token sent as a Bearer token, or from a signed-in session. A read-only token is refused, and workspace API keys aren't accepted.</Note>","operationId":"delete_font_api_fonts__font_id__delete","parameters":[{"name":"font_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the font to delete. Get it from `id` in [List workspace fonts](/api-reference/list-workspace-fonts).","title":"Font Id"},"description":"ID of the font to delete. Get it from `id` in [List workspace fonts](/api-reference/list-workspace-fonts).","example":"68d4b2e1f0a9c8001b3e5f27"}],"responses":{"204":{"description":"The font was deleted."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You aren't an owner or admin of the workspace, your token is read-only, or your credential can't be used on this endpoint."},"404":{"description":"No font with this ID in the workspace."},"409":{"description":"Your workspace requires an unlocked SSO session."},"429":{"description":"Rate limit exceeded."}}}},"/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\nA workflow's `name` is unique per app across everything that is not archived. Reusing a name is rejected.\n\nThis is only allowed from the app's main branch.\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, or the app is on a branch other than main."},"422":{"description":"The definition or trigger is not valid. The body lists what is wrong."},"429":{"description":"Rate limit exceeded. The base limit is 20 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"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\nReturns 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. Get a single workflow with [Get workflow](/api-reference/get-workflow) when you need the definition.\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_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."},"429":{"description":"Rate limit exceeded. The base limit is 60 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"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\nReturns runs across every workflow on the app, newest first.\n\nSet `status` to return only runs in one state. Set `since` to return only runs that started after a moment in time.\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_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."},"429":{"description":"Rate limit exceeded. The base limit is 60 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"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 AI create it. Nothing is saved by calling this.\n\nThe `page` param 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\nThe `suggestions` field can come back empty when the model returns nothing usable. That is still a successful call.","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, and anything higher returns the last set.","default":0,"title":"Page"},"description":"Which set of ideas to return: `0` and `1` give different sets, and anything higher returns the last set.","example":0}],"responses":{"200":{"description":"Workflow ideas for this app.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowSuggestions"}}}},"429":{"description":"Rate limit exceeded. The base limit is 5 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"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 can span at most 30 days. Ask for a longer one and the call still succeeds, but `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. Set `since` and `until` to choose your own window, up to that cap. Leave both out for the last 24 hours.","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."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"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\nReturns the specified workflow, including the full definition it currently runs.\n\nThis is the only endpoint that returns `definition`.\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_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"}}}},"429":{"description":"Rate limit exceeded. The base limit is 60 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"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. Pass `change_summary` to label what changed.\n\nAnything you leave out keeps its stored value. Sending a `definition` that differs from the current one saves it as a new immutable version, and the workflow runs that version from then on. Earlier versions stay readable through [List workflow versions](/api-reference/list-workflow-versions).\n\nUpdating does not change whether the workflow is running.\n\nThis is only allowed from the app's main branch.\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, or the app is on a branch other than main."},"422":{"description":"The definition or trigger is not valid. The body lists what is wrong."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"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 and stops it responding to its trigger. 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.\n\nThis is only allowed from the app's main branch.","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"}}}},"409":{"description":"The app is on a branch other than main."},"422":{"description":"The workflow could not be archived. The body says why."},"429":{"description":"Rate limit exceeded. The base limit is 20 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"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. The `message` field says which, in words you can show someone.\n\nRestoring fails when another live workflow already uses this one's name, or the name of its file in the app's code, since it was archived. Rename the other workflow, or use [Update workflow](/api-reference/update-workflow) to rename this one first, then retry.\n\nIf the restore can't fully complete, the workflow lands inactive rather than active, and the call reports the failure.\n\nThis is only allowed from the app's main branch.","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, or the app is on a branch other than main."},"422":{"description":"The workflow could not be restored. The body says why."},"429":{"description":"Rate limit exceeded. The base limit is 20 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"502":{"description":"The restore could not fully complete. The workflow lands inactive rather than active."},"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\nToggles a workflow between `active` and `inactive`, based on whichever it currently is.\n\nPausing takes effect immediately. The schedule is removed and the trigger stops firing. Starting a workflow again requires a plan that includes workflows, but pausing doesn't, so a workspace that has lost the capability can still stop a workflow it can no longer start.\n\nArchived workflows cannot be toggled. Restore one first with [Restore workflow](/api-reference/restore-workflow).\n\nToggling in either direction, including pausing, is only allowed from the app's main branch.","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."},"409":{"description":"The app is on a branch other than main."},"422":{"description":"The status could not be changed. The body says why."},"429":{"description":"Rate limit exceeded. The base limit is 20 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"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 doesn't fail the call. It comes back with `valid` set to `false` and the problems listed in `errors`, so branch on `valid` rather than on whether the call succeeded.","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"}}}},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"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\nReturns the workflow's saved versions, newest first.\n\nEvery definition change saves a version, so this is the history of what the workflow has run. Get a single version with [Get workflow version](/api-reference/get-workflow-version).\n\nThe `limit` param 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.","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"}}}},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute, and this endpoint shares it with [Get workflow version](/api-reference/get-workflow-version). See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"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\nReturns the specified 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).","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."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute, and this endpoint shares it with [List workflow versions](/api-reference/list-workflow-versions). See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/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\nIf `manual_run_config.requires_previous_run` is `false`, this workflow doesn't need a payload. Call [Run a workflow now](/api-reference/run-a-workflow-now) with an empty body.\n\nIf it's `true`, the trigger's payload is real data pulled from the app, not something you can invent. For example, an entity trigger's payload is the actual record that was created or changed, and its fields feed directly into the workflow's steps. Rather than inventing one, replay a real payload from a past run. Pick one of the `recent_runs` entries and pass its `run_id` as `replay_from_run_id` when you call [Run a workflow now](/api-reference/run-a-workflow-now).\n\nEvery run listed in `recent_runs` is confirmed replayable, so using one should always succeed. The list holds at most 20 runs, newest first. It's empty when `requires_previous_run` is `false`, since there's nothing to replay, and it can also be empty when it's `true` but no past run has a payload that's still replayable yet, for example before the workflow has ever run.","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"}}}},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"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. A `replay_from_run_id` whose payload no longer matches the workflow's current trigger is rejected, because replaying it would run the steps against fields that have since moved.","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."},"429":{"description":"Rate limit exceeded. The base limit is 5 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"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\nReturns the 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\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_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."},"429":{"description":"Rate limit exceeded. The base limit is 60 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"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\nReturns the specified run with its step-by-step log.\n\nThe `steps` field walks the tasks the run executed in order, each with its status, timing, and error if it failed. The `definition_version_id` field 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\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_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."},"429":{"description":"Rate limit exceeded. The base limit is 120 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/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 is rejected.\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 the call still succeeds, with `explanation` and `generated_at` both empty rather than an error.","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."},"429":{"description":"Rate limit exceeded. The base limit is 5 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/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\nRequests that a running workflow stop.\n\nThe `status` field 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. The call succeeds and carries that run's current status, and nothing changes, so a cancel racing a run that just completed is safe to retry.","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."},"429":{"description":"Rate limit exceeded. The base limit is 10 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/emails/system/{email_type}/preview":{"get":{"summary":"Preview application email","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRenders one of the app's application emails as recipients get it, with sample values for their details.\n\nBase44 sends four application emails for the app: password reset (`password_reset`), sign-up verification (`email_verification`), invitation (`user_invite`), and access approval (`access_approved`). They use the app's brand automatically. An app can send its own version of each instead, created with [Customize application email](/api-reference/customize-application-email). The preview shows the app's version whenever it's the one sent.\n\nThis is limited to 20 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\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":"preview_system_email_api_apps__app_id__emails_system__email_type__preview_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the emails belong to.","title":"App Id"},"description":"ID of the app the emails belong to.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"email_type","in":"path","required":true,"schema":{"type":"string","description":"The application email: `password_reset`, `email_verification`, `user_invite`, or `access_approved`.","title":"Email Type"},"description":"The application email: `password_reset`, `email_verification`, `user_invite`, or `access_approved`.","example":"password_reset"}],"responses":{"200":{"description":"The rendered email.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationEmailPreview"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found, or `email_type` isn't one of the four application emails."},"429":{"description":"Rate limit exceeded. The base limit is 20 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/emails/system/{email_type}/materialize":{"post":{"summary":"Customize application email","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nGives the app its own version of an application email, as an email template you can change. The template starts as a copy of the email the app sends today, with placeholders such as `{{first_name}}` and `{{action_url}}` where the recipient's details go. Ask the AI to change it through [Send chat message](/api-reference/send-chat-message).\n\nBase44 sends four application emails for the app: password reset (`password_reset`), sign-up verification (`email_verification`), invitation (`user_invite`), and access approval (`access_approved`). They use the app's brand automatically. From now on, the app sends its own version. For `email_verification`, that also needs the app to send from its own email domain and the version to pass a review, and [Preview application email](/api-reference/preview-application-email) reports in `authored_status` which version is sent. Delete the template with [Delete email template](/api-reference/delete-email-template) to go back to the Base44 version.\n\nCalling this again returns the template the app already has, with `created` set to `false`.\n\nCreating the template needs a paid workspace plan.\n\nThis is limited to 10 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"materialize_system_email_route_api_apps__app_id__emails_system__email_type__materialize_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the emails belong to.","title":"App Id"},"description":"ID of the app the emails belong to.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"email_type","in":"path","required":true,"schema":{"type":"string","description":"The application email: `password_reset`, `email_verification`, `user_invite`, or `access_approved`.","title":"Email Type"},"description":"The application email: `password_reset`, `email_verification`, `user_invite`, or `access_approved`.","example":"password_reset"}],"responses":{"200":{"description":"The app's template for the email.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomizedApplicationEmail"}}}},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The app has no template for this email yet, and the workspace's plan doesn't include one."},"403":{"description":"You don't have editor access to this app, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found, or `email_type` isn't one of the four application emails."},"429":{"description":"Rate limit exceeded. The base limit is 10 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/emails/system/{email_type}/test":{"post":{"summary":"Send application email test","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSends you a test copy of one of the app's application emails, exactly as [Preview application email](/api-reference/preview-application-email) renders it, sample values included.\n\nThe test goes only to the email address of the user your credential belongs to. You can't choose another recipient.\n\nThis is limited to 5 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"send_system_test_email_to_self_api_apps__app_id__emails_system__email_type__test_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the emails belong to.","title":"App Id"},"description":"ID of the app the emails belong to.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"email_type","in":"path","required":true,"schema":{"type":"string","description":"The application email: `password_reset`, `email_verification`, `user_invite`, or `access_approved`.","title":"Email Type"},"description":"The application email: `password_reset`, `email_verification`, `user_invite`, or `access_approved`.","example":"password_reset"}],"responses":{"200":{"description":"The test was sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestEmailSent"}}}},"400":{"description":"The user your credential belongs to has no email address."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found, or `email_type` isn't one of the four application emails."},"429":{"description":"Rate limit exceeded. The base limit is 5 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/emails":{"get":{"summary":"List email templates","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 email templates, newest first, without their HTML. Get a template's HTML with [Get email template](/api-reference/get-email-template).\n\nAn app's email templates are files in its code, in `base44/emails/`. The AI creates and edits them through [Send chat message](/api-reference/send-chat-message).\n\nYou get up to `limit` templates, at most 200, and there's no pagination.\n\nThis is limited to 60 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit. [Get email stats](/api-reference/get-email-stats) shares it.\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>\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_email_templates_api_apps__app_id__emails_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the emails belong to.","title":"App Id"},"description":"ID of the app the emails belong to.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Maximum number of templates to return, from 1 to 200. Defaults to 30.","default":30,"title":"Limit"},"description":"Maximum number of templates to return, from 1 to 200. Defaults to 30.","example":50}],"responses":{"200":{"description":"The app's email templates, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplates"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"422":{"description":"`limit` is outside 1 to 200."},"429":{"description":"Rate limit exceeded. The base limit is 60 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/emails/stats":{"get":{"summary":"Get email 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 delivery and engagement counts for the emails the app sent, for each template and in total, over the last `days` days. Each count has a daily breakdown and the totals for the same number of days before, so you can compare periods.\n\nDays are UTC. A send counts on the day it was sent, and a delivery, open or click on the day it happened, so a rate can include outcomes of emails sent before the period. Opens and clicks are counted only for emails sent with tracking on. Emails the app sends from its own email domain aren't counted yet, so an app that sends from its own domain gets zeros for those.\n\n`templates` is paginated. Pass `next_cursor` back as `cursor`, with the same `days` and `limit`, to get the next page. Every page carries the same app-wide `totals`, `previous_totals` and `daily`.\n\nEmail stats need a paid workspace plan.\n\nThis is limited to 60 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit. [List email templates](/api-reference/list-email-templates) shares it.\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":"get_email_stats_api_apps__app_id__emails_stats_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the emails belong to.","title":"App Id"},"description":"ID of the app the emails belong to.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":90,"minimum":1,"description":"Number of days to count, from 1 to 90, ending today in UTC. Defaults to 30. The previous totals cover the same number of days just before.","default":30,"title":"Days"},"description":"Number of days to count, from 1 to 90, ending today in UTC. Defaults to 30. The previous totals cover the same number of days just before.","example":30},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Maximum number of entries in `templates` per page, from 1 to 200. Defaults to 50.","default":50,"title":"Limit"},"description":"Maximum number of entries in `templates` per page, from 1 to 200. Defaults to 50.","example":50},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`next_cursor` from the previous page. Send the same `days` and `limit` with it. A cursor expires after 24 hours.","title":"Cursor"},"description":"`next_cursor` from the previous page. Send the same `days` and `limit` with it. A cursor expires after 24 hours.","example":"eyJuIjoiYXBwX2VtYWlsX3N0YXRzIiwicCI6eyJhZnRlciI6IldlbGNvbWUifX0"}],"responses":{"200":{"description":"The app's email stats.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStats"}}}},"400":{"description":"`cursor` is invalid or expired, or doesn't match the `days` and `limit` you sent with it."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The workspace's plan doesn't include email stats."},"403":{"description":"You don't have access to this app, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"422":{"description":"`days` is outside 1 to 90, or `limit` is outside 1 to 200."},"429":{"description":"Rate limit exceeded. The base limit is 60 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/emails/{template_id}":{"get":{"summary":"Get email template","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 of the app's email templates with its HTML.\n\nAn app's email templates are files in its code, in `base44/emails/`. The AI creates and edits them through [Send chat message](/api-reference/send-chat-message).\n\nThis is limited to 60 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\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>\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_email_template_api_apps__app_id__emails__template_id__get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the emails belong to.","title":"App Id"},"description":"ID of the app the emails belong to.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"template_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the email template to get. Get it from `id` in [List email templates](/api-reference/list-email-templates).","title":"Template Id"},"description":"ID of the email template to get. Get it from `id` in [List email templates](/api-reference/list-email-templates).","example":"68c1f0a2b3d4e5f6a7b8c9d0"}],"responses":{"200":{"description":"The email template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateDetail"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found, or the app has no email template with this ID."},"429":{"description":"Rate limit exceeded. The base limit is 60 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}},"delete":{"summary":"Delete email template","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nDeletes an email template by removing its file from the app's code. This can't be undone.\n\nWorkflows and backend functions that send the template by name keep it in their code, and those sends fail once it's gone. Deleting the app's own version of an application email, such as `PasswordReset`, makes the app send the Base44 version again.\n\nIf the file can't be removed, the template isn't deleted, and you can retry.\n\nThis is limited to 20 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"delete_email_template_api_apps__app_id__emails__template_id__delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the emails belong to.","title":"App Id"},"description":"ID of the app the emails belong to.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"template_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the email template to delete. Get it from `id` in [List email templates](/api-reference/list-email-templates).","title":"Template Id"},"description":"ID of the email template to delete. Get it from `id` in [List email templates](/api-reference/list-email-templates).","example":"68c1f0a2b3d4e5f6a7b8c9d0"}],"responses":{"200":{"description":"The template was deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedEmailTemplate"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found, or the app has no email template with this ID."},"429":{"description":"Rate limit exceeded. The base limit is 20 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/emails/{template_id}/test":{"post":{"summary":"Send email template test","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSends you a test copy of an email template. The subject is the template's `<title>`, or its name when it has none, with `[Test]` in front. `{{variable}}` placeholders are sent as written.\n\nThe test goes only to the email address of the user your credential belongs to. You can't choose another recipient.\n\nThis is limited to 5 requests per minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"send_test_email_to_self_api_apps__app_id__emails__template_id__test_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app the emails belong to.","title":"App Id"},"description":"ID of the app the emails belong to.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"template_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the email template to send. Get it from `id` in [List email templates](/api-reference/list-email-templates).","title":"Template Id"},"description":"ID of the email template to send. Get it from `id` in [List email templates](/api-reference/list-email-templates).","example":"68c1f0a2b3d4e5f6a7b8c9d0"}],"responses":{"200":{"description":"The test was sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestEmailSent"}}}},"400":{"description":"The user your credential belongs to has no email address."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, the app is blocked, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found, or the app has no email template with this ID."},"429":{"description":"Rate limit exceeded. The base limit is 5 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/external-auth/connection-config/{integration_type}":{"get":{"summary":"Get connector connection fields","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 values a connector needs before you [start a connection](/api-reference/start-connector-connection), such as the account subdomain for Snowflake, and the values saved on the app's current connection. Most connectors need none.\n\nSend them as `connection_config` when you start the connection.\n\nThis is limited to 150 requests a minute per caller. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app, including a read-only key. Workspace API keys are not accepted.</Note>","operationId":"get_connection_config_api_apps__app_id__external_auth_connection_config__integration_type__get","parameters":[{"name":"integration_type","in":"path","required":true,"schema":{"type":"string","description":"ID of the connector, from `integration_type` in [List connectors](/api-reference/list-connectors).","title":"Integration Type"},"description":"ID of the connector, from `integration_type` in [List connectors](/api-reference/list-connectors).","example":"googlecalendar"},{"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 connector's fields and the app's saved values.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionConfigResponse"}}}},"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 connector isn't available to you."},"409":{"description":"The app's workspace requires an unlocked SSO session."},"429":{"description":"Rate limit reached. Retry later."}}}},"/api/apps/{app_id}/external-auth/initiate":{"post":{"summary":"Start connector connection","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nStarts connecting a third-party account to the app with OAuth, and returns a link where a person signs in to the provider and approves access. The connection is recorded as yours, whichever provider account approves it.\n\nConnecting is done in steps:\n1. Call this endpoint and open `redirect_url` in a browser.\n2. Sign in to the provider and approve the requested scopes. This has to happen within five minutes of the call, and the link itself stops working after 10 minutes.\n3. Poll [Get connector connection status](/api-reference/get-connector-connection-status) with the returned `connection_id` until it's `ACTIVE` or `FAILED`.\n\n<Warning>Treat `redirect_url` as a credential. Whoever approves access through it connects their provider account to the app.</Warning>\n\nA successful call doesn't always start an authorization. Read `already_authorized` and `error` first. When the app already has a working connection that covers the scopes, there's nothing to open. When another collaborator's account is connected, the call reports `different_user`. With `integration_type`, send `force_reconnect` to replace it with yours.\n\nRetrying starts a new authorization with a new link rather than returning the earlier one, so open the link from the latest response.\n\nSend `integration_type` to connect through Base44's OAuth app, or `connector_id` for a workspace connector that uses the workspace's own OAuth app. The call is refused when the workspace has turned off builder connections for the connector, and connectors with an `auth_method` of `platform_credentials` can't be connected this way.\n\nThis is limited to 15 requests a minute per caller. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"initiate_connection_api_apps__app_id__external_auth_initiate_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/InitiateConnectionRequest"}}}},"responses":{"200":{"description":"The authorization was started, or the response says why none was needed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InitiateConnectionResponse"}}}},"400":{"description":"Both or neither of `integration_type` and `connector_id` were sent, a `connection_config` value is missing or invalid, the connector can't be connected this way, or `connector_id` isn't a connector in the app's workspace."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The app's workspace plan doesn't include connectors."},"403":{"description":"You don't have editor access to this app, you're a viewer in its workspace, your API key is read-only, or you used a workspace API key. Also returned when the workspace has turned off builder connections for the connector."},"404":{"description":"App not found, or the connector isn't available to you."},"409":{"description":"The app's store already uses this provider, so its connector can't also be connected, or the app's workspace requires an unlocked SSO session."},"422":{"description":"The body isn't a JSON object, or a field has the wrong type or format."},"429":{"description":"Rate limit reached. Retry later."}}}},"/api/apps/{app_id}/external-auth/integrations/{integration_type}":{"put":{"summary":"Set connector scopes","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 app's connection for a connector to exactly the scopes you send, and returns a link to authorize them when that takes a new authorization.\n\n[Start connector connection](/api-reference/start-connector-connection) adds scopes to what the app already has. This endpoint replaces them, so scopes you leave out are dropped once the new authorization completes. Scopes Base44's OAuth app doesn't allow for the connector are dropped without an error, and scopes the connector always needs are added.\n\nRead `already_authorized` and `error` first:\n- When you already have an active connection with exactly these scopes and connection values, `already_authorized` is `true` and there's nothing to open. Calling again changes nothing.\n- When another collaborator's active connection has exactly these scopes, the call reports `different_user`. To replace it with yours, use Start connector connection with `force_reconnect`.\n- Otherwise, including when the scopes or connection values differ from another collaborator's connection, a new authorization starts. Completing it replaces the app's current connection.\n\nTo authorize, open `redirect_url` in a browser, approve the scopes within five minutes of the call, and poll [Get connector connection status](/api-reference/get-connector-connection-status) with `connection_id` until it's `ACTIVE` or `FAILED`. The link stops working after 10 minutes. Until the authorization completes, each call starts a new one with a new link.\n\n<Warning>Treat `redirect_url` as a credential. Whoever approves access through it connects their provider account to the app.</Warning>\n\nThe call is refused when the workspace has turned off builder connections for the connector. It works only for connectors that Base44's OAuth app can connect: a `connector_mode` of `all` or `shared_only` in [List connectors](/api-reference/list-connectors), with an `auth_method` other than `platform_credentials`.\n\nThis is limited to 15 requests a minute per caller. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"set_integration_api_apps__app_id__external_auth_integrations__integration_type__put","parameters":[{"name":"integration_type","in":"path","required":true,"schema":{"anyOf":[{"enum":["googlecalendar","google_classroom","googledrive","gmail","googlesheets","googledocs","googleslides","googlebigquery","googlemeet","googletasks","googleads","google_analytics","google_search_console","slack","notion","salesforce","hubspot","linkedin","tiktok","instagram","discord","slackbot","wix","github","gitlab","bamboohr","dropbox","clickup","wrike","box","outlook","linear","airtable","microsoft_teams","share_point","one_drive","typeform","splitwise","hugging_face","calendly","contentful","supabase","snowflake","databricks","quickbooks","square"],"type":"string"},{"type":"string","minLength":1,"maxLength":80,"pattern":"^[a-z0-9_]+$"}],"description":"ID of the connector, from `integration_type` in [List connectors](/api-reference/list-connectors).","title":"Integration Type"},"description":"ID of the connector, from `integration_type` in [List connectors](/api-reference/list-connectors).","example":"googlecalendar"},{"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/SetIntegrationRequest"}}}},"responses":{"200":{"description":"The authorization was started, or the response says why none was needed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetIntegrationResponse"}}}},"400":{"description":"The connector can't be connected through Base44's OAuth app, or a `connection_config` value is missing or invalid."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The app's workspace plan doesn't include connectors."},"403":{"description":"You don't have editor access to this app, you're a viewer in its workspace, your API key is read-only, or you used a workspace API key. Also returned when the workspace has turned off builder connections for the connector."},"404":{"description":"App not found, or the connector isn't available to you."},"409":{"description":"The app's store already uses this provider, so its connector can't also be connected, or the app's workspace requires an unlocked SSO session."},"422":{"description":"`integration_type` isn't a valid connector ID, or the body has a missing or wrongly typed field."},"429":{"description":"Rate limit reached. Retry later."}}},"delete":{"summary":"Disconnect connector","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nDisconnects a connector from the app and stops the app from using the connected account.\n\nWhen the connection belongs to the app, its stored tokens are deleted. When it's a workspace connection shared with other apps, only this app is detached, and the others keep using it. Either way, the app's backend functions and agents can no longer use the connector, and any events the provider sends for it stop reaching the app.\n\nHow the two compare:\n- [Disconnect connector](/api-reference/disconnect-connector) keeps the connector on the app, marked `disconnected` with its scopes, so it can be connected again with the same scopes.\n- [Remove connector](/api-reference/remove-connector) deletes it from the app, including its entry in the app's code.\n\nThe call succeeds even when the app has no connection for the connector.\n\nThis is limited to 10 requests a minute per caller. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"disconnect_integration_api_apps__app_id__external_auth_integrations__integration_type__delete","parameters":[{"name":"integration_type","in":"path","required":true,"schema":{"anyOf":[{"enum":["googlecalendar","google_classroom","googledrive","gmail","googlesheets","googledocs","googleslides","googlebigquery","googlemeet","googletasks","googleads","google_analytics","google_search_console","slack","notion","salesforce","hubspot","linkedin","tiktok","instagram","discord","slackbot","wix","github","gitlab","bamboohr","dropbox","clickup","wrike","box","outlook","linear","airtable","microsoft_teams","share_point","one_drive","typeform","splitwise","hugging_face","calendly","contentful","supabase","snowflake","databricks","quickbooks","square"],"type":"string"},{"type":"string","minLength":1,"maxLength":80,"pattern":"^[a-z0-9_]+$"}],"description":"ID of the connector, from `integration_type` in [List connectors](/api-reference/list-connectors).","title":"Integration Type"},"description":"ID of the connector, from `integration_type` in [List connectors](/api-reference/list-connectors).","example":"googlecalendar"},{"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 connector is disconnected from the app.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisconnectIntegrationResult"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you're a viewer in its workspace, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found, or the connector isn't available to you and the app has no connection for it."},"409":{"description":"The app's workspace requires an unlocked SSO session."},"422":{"description":"`integration_type` isn't a valid connector ID."},"429":{"description":"Rate limit reached. Retry later."}}}},"/api/apps/{app_id}/external-auth/status":{"get":{"summary":"Get connector connection status","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns where a connector authorization you started with [Start connector connection](/api-reference/start-connector-connection) stands. Poll it every few seconds after opening the link, until `status` is `ACTIVE` or `FAILED`.\n\nAn authorization nobody completes stays `PENDING`, so stop polling a few minutes after the link expires. A connection another collaborator started always reads as `PENDING` to you.\n\nThis is limited to 70 requests a minute per caller. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app, including a read-only key. Workspace API keys are not accepted.</Note>","operationId":"get_integration_status_api_apps__app_id__external_auth_status_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":"integration_type","in":"query","required":true,"schema":{"anyOf":[{"enum":["googlecalendar","google_classroom","googledrive","gmail","googlesheets","googledocs","googleslides","googlebigquery","googlemeet","googletasks","googleads","google_analytics","google_search_console","slack","notion","salesforce","hubspot","linkedin","tiktok","instagram","discord","slackbot","wix","github","gitlab","bamboohr","dropbox","clickup","wrike","box","outlook","linear","airtable","microsoft_teams","share_point","one_drive","typeform","splitwise","hugging_face","calendly","contentful","supabase","snowflake","databricks","quickbooks","square"],"type":"string"},{"type":"string","minLength":1,"maxLength":80,"pattern":"^[a-z0-9_]+$"}],"description":"Connector the authorization is for.","title":"Integration Type"},"description":"Connector the authorization is for.","example":"googlecalendar"},{"name":"connection_id","in":"query","required":true,"schema":{"type":"string","description":"`connection_id` returned by [Start connector connection](/api-reference/start-connector-connection).","title":"Connection Id"},"description":"`connection_id` returned by [Start connector connection](/api-reference/start-connector-connection).","example":"base44_6820f3a4e7b91d003c45a1f7"}],"responses":{"200":{"description":"Where the authorization stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntegrationPollingStatusResult"}}}},"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 connector isn't available to you and the app has no connection for it."},"409":{"description":"The app's workspace requires an unlocked SSO session."},"422":{"description":"`integration_type` or `connection_id` is missing, or `integration_type` isn't a valid connector ID."},"429":{"description":"Rate limit reached. Retry later."}}}},"/api/apps/{app_id}/external-auth/integrations/{integration_type}/remove":{"delete":{"summary":"Remove connector","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 connector from the app completely.\n\nIt does everything [Disconnect connector](/api-reference/disconnect-connector) does, then deletes the connector from the app, including the connector file in the app's code. Connecting it again starts from scratch.\n\nHow the two compare:\n- [Disconnect connector](/api-reference/disconnect-connector) keeps the connector on the app, marked `disconnected` with its scopes, so it can be connected again with the same scopes.\n- [Remove connector](/api-reference/remove-connector) deletes it from the app, including its entry in the app's code.\n\nThe call succeeds even when the app has no connection for the connector.\n\nThis is limited to 10 requests a minute per caller. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"remove_integration_api_apps__app_id__external_auth_integrations__integration_type__remove_delete","parameters":[{"name":"integration_type","in":"path","required":true,"schema":{"anyOf":[{"enum":["googlecalendar","google_classroom","googledrive","gmail","googlesheets","googledocs","googleslides","googlebigquery","googlemeet","googletasks","googleads","google_analytics","google_search_console","slack","notion","salesforce","hubspot","linkedin","tiktok","instagram","discord","slackbot","wix","github","gitlab","bamboohr","dropbox","clickup","wrike","box","outlook","linear","airtable","microsoft_teams","share_point","one_drive","typeform","splitwise","hugging_face","calendly","contentful","supabase","snowflake","databricks","quickbooks","square"],"type":"string"},{"type":"string","minLength":1,"maxLength":80,"pattern":"^[a-z0-9_]+$"}],"description":"ID of the connector, from `integration_type` in [List connectors](/api-reference/list-connectors).","title":"Integration Type"},"description":"ID of the connector, from `integration_type` in [List connectors](/api-reference/list-connectors).","example":"googlecalendar"},{"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 connector is removed from the app.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveIntegrationResult"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you're a viewer in its workspace, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found, or the connector isn't available to you and the app has no connection for it."},"409":{"description":"The app's workspace requires an unlocked SSO session."},"422":{"description":"`integration_type` isn't a valid connector ID."},"429":{"description":"Rate limit reached. Retry later."}}}},"/api/apps/{app_id}/external-auth/list":{"get":{"summary":"List connector connections","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 connector connections, whether active, expired, or disconnected, with who made each one and the scopes it was granted. Read `status` to tell whether a connection still works. Connections made with a workspace connector's own OAuth app aren't included.\n\nEach active connection is checked as you list it, so `status` can read `expired` before the app next uses the connector. The list isn't paginated, since an app has at most one connection for each connector.\n\nThis is limited to 60 requests a minute per caller. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app, including a read-only key. Workspace API keys are not accepted.</Note>","operationId":"list_integrations_api_apps__app_id__external_auth_list_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":"The app's connector connections.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListIntegrationsResponse"}}}},"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."},"409":{"description":"The app's workspace requires an unlocked SSO session."},"429":{"description":"Rate limit reached. Retry later."}}}},"/api/apps/{app_id}/external-auth/connector-governance":{"get":{"summary":"Get connector policies","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 connector policies of the app's workspace, one entry per connector. It tells you whether builders may connect each connector to the workspace's apps, and whether the apps' users may connect their own accounts.\n\nA connector with `builder_connection_enabled` set to `false` can't be connected with [Start connector connection](/api-reference/start-connector-connection). Workspace admins set these policies in the workspace settings. Any editor of the app can read them, including one who isn't a member of the workspace.\n\nThe list isn't paginated.\n\nThis is limited to 60 requests a minute per caller. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app, including a read-only key. Workspace API keys are not accepted.</Note>","operationId":"list_app_connector_governance_api_apps__app_id__external_auth_connector_governance_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":"The workspace's connector policies.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorGovernanceListResponse"}}}},"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."},"409":{"description":"The app's workspace requires an unlocked SSO session."},"429":{"description":"Rate limit reached. Retry later."}}}},"/api/external-auth/available-integrations":{"get":{"summary":"List connectors","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 connector you can connect to an app, with what each one needs before authorization. Use `integration_type` from here on the app connector endpoints.\n\nThe list can differ between callers, because some connectors reach only some accounts while they roll out. It isn't paginated.\n\nThis is limited to 60 requests a minute per caller. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key, including a read-only key. Workspace API keys are not accepted.</Note>","operationId":"list_available_integrations_global_api_external_auth_available_integrations_get","responses":{"200":{"description":"The connectors available to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListIntegrationTypesResponse"}}}},"401":{"description":"Missing or invalid credentials."},"409":{"description":"Your active workspace requires an unlocked SSO session."},"429":{"description":"Rate limit reached. Retry later."}}}},"/api/apps/{app_id}/payments/stripe/sandbox":{"get":{"summary":"Get Stripe sandbox","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 stored [Stripe sandbox](/developers/references/apps-api/sections/payments-stripe#from-sandbox-to-live), or `null` if none exists.\n\nUse this when you need the sandbox's own fields, such as its Stripe ID or exact expiration time, rather than the summary [Get Stripe integration status](/api-reference/get-stripe-integration-status) reports.\n\nThis read does not refresh the sandbox's state from Stripe. Call [Get Stripe onboarding link](/api-reference/get-stripe-onboarding-link) instead if you need it refreshed.\n\nRequires read access to the app.","operationId":"get_sandbox_status_api_apps__app_id__payments_stripe_sandbox_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/SandboxStatusResponse"},{"type":"null"}],"title":"Response Get Sandbox Status Api Apps  App Id  Payments Stripe Sandbox Get"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential is not permitted or you don't have the required app access. A missing app also fails the app access check. Setup and catalog operations also return `app_payment_management_not_allowed` when the app's workspace restricts payment management."},"404":{"description":"The app is outside the credential's granted workspace."},"409":{"description":"The workspace requires an unlocked SSO session."}}}},"/api/apps/{app_id}/payments/stripe/sandbox/claim-url":{"get":{"summary":"Get Stripe onboarding link","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 link to claim a [sandbox](/developers/references/apps-api/sections/payments-stripe#from-sandbox-to-live) or resume an unfinished activation.\n\nThis call checks Stripe for the sandbox's current state and updates the stored record if it's out of date. It also renews the link itself if the stored one expired.\n\nRequires write access to the app. Workspace viewers cannot call this operation.","operationId":"get_sandbox_claim_url_api_apps__app_id__payments_stripe_sandbox_claim_url_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimUrlResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential is not permitted or you don't have the required app access. A missing app also fails the app access check. Setup and catalog operations also return `app_payment_management_not_allowed` when the app's workspace restricts payment management."},"404":{"description":"No Stripe sandbox exists or the app is outside the credential grant."},"409":{"description":"The workspace requires an unlocked SSO session."}}}},"/api/apps/{app_id}/payments/stripe/install":{"post":{"summary":"Install Stripe integration","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSets up a Stripe [sandbox](/developers/references/apps-api/sections/payments-stripe#from-sandbox-to-live) for the app and stores test credentials.\n\nCalling this again when the app already has a usable Stripe installation returns the existing installation instead of creating a duplicate.\n\nIf the app has a Wix store, that store already owns the app's checkout and this call is blocked, since a store and a payment provider can't both own it. Payment providers don't enforce that same exclusivity against each other, though. An active Wix Payments or Payments by Wix connection doesn't block this call, and installing Stripe alongside such a connection leaves both connected.\n\nRequires write access to the app. Workspace viewers cannot call this operation.","operationId":"install_stripe_integration_api_apps__app_id__payments_stripe_install_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstallStripeResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential is not permitted or you don't have the required app access. A missing app also fails the app access check. Setup and catalog operations also return `app_payment_management_not_allowed` when the app's workspace restricts payment management."},"404":{"description":"The app is outside the credential's granted workspace."},"409":{"description":"Another installation is in progress, a Wix store already owns the app's checkout, or the workspace requires an unlocked SSO session."}}}},"/api/apps/{app_id}/payments/stripe/configure-live-keys":{"post":{"summary":"Configure live Stripe keys","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSwitches the app to live mode using live Stripe keys you already have from the Stripe dashboard, sent directly in the request body, rather than keys Base44 creates for you. Use [Provision live Stripe keys](/api-reference/provision-live-stripe-keys) instead to have Base44 create them automatically once the sandbox's Stripe account is activated.\n\nStores the keys, backs up the current test credentials, and redeploys backend functions with the updated secrets.\n\nKey prefixes are checked, but success does not verify that the keys belong to the same Stripe account or can process payments.\n\nAutomatic webhook migration is best effort.\n\nRequires write access to the app. Workspace viewers cannot call this operation.","operationId":"configure_live_keys_api_apps__app_id__payments_stripe_configure_live_keys_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfigureLiveKeysRequest"}}}},"responses":{"200":{"description":"The operation result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StripeLiveKeysResult"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential is not permitted or you don't have the required app access. A missing app also fails the app access check. Setup and catalog operations also return `app_payment_management_not_allowed` when the app's workspace restricts payment management."},"404":{"description":"No Stripe sandbox exists or the app is outside the credential grant."},"409":{"description":"The workspace requires an unlocked SSO session."},"400":{"description":"A key has an invalid prefix."},"422":{"description":"The body is missing, `publishable_key` or `secret_key` is missing, or a value doesn't match its field's type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/payments/stripe/provision-live-keys":{"post":{"summary":"Provision live Stripe keys","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nCreates live Stripe keys automatically through Stripe, rather than keys you provide yourself. Use [Configure live Stripe keys](/api-reference/configure-live-stripe-keys) instead if you already have live keys from Stripe.\n\nWhat happens depends on the app's current state:\n\n- If no live key is stored yet, this creates one. The [sandbox](/developers/references/apps-api/sections/payments-stripe#from-sandbox-to-live) must be claimed and its Stripe account activated first.\n- If a live key is already stored but setup never finished, this completes it using that key, without requiring activation.\n- If setup already finished, this reports success without doing anything further.\n\nRequires write access to the app. Workspace viewers cannot call this operation.","operationId":"provision_live_keys_api_apps__app_id__payments_stripe_provision_live_keys_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvisionLiveKeysResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential is not permitted or you don't have the required app access. A missing app also fails the app access check. Setup and catalog operations also return `app_payment_management_not_allowed` when the app's workspace restricts payment management."},"404":{"description":"No Stripe sandbox exists or the app is outside the credential grant."},"409":{"description":"The workspace requires an unlocked SSO session."},"400":{"description":"The Stripe account is not activated and no partial live setup can be recovered."},"429":{"description":"The base limit is 5 requests per 60 seconds, scoped to your account rather than the workspace. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/payments/stripe/status":{"get":{"summary":"Get Stripe integration 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 how far Stripe setup has gone for the app, without returning any credentials.\n\nIt covers the [sandbox](/developers/references/apps-api/sections/payments-stripe#from-sandbox-to-live)'s status and claim state, and whether test or live credentials are stored.\n\nIf Stripe was never set up, every field but `app_id` comes back `false` or `null`.\n\nRequires read access to the app.","operationId":"get_stripe_integration_status_api_apps__app_id__payments_stripe_status_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"check_transactions","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to look for any checkout session in sandbox mode. Defaults to `false`.","default":false,"title":"Check Transactions"},"description":"Whether to look for any checkout session in sandbox mode. Defaults to `false`.","example":false}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StripeIntegrationStatusResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential is not permitted or you don't have the required app access. A missing app also fails the app access check. Setup and catalog operations also return `app_payment_management_not_allowed` when the app's workspace restricts payment management."},"404":{"description":"The app is outside the credential's granted workspace."},"409":{"description":"The workspace requires an unlocked SSO session."},"422":{"description":"`check_transactions` isn't a valid boolean.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/payments/stripe/products":{"post":{"summary":"Create Stripe product","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 product.\n\nUses the Stripe account selected by the app's stored credentials. Live keys select the live catalog and test keys select the test catalog. Products and prices belong to that Stripe account, so multiple apps configured with the same account share the same catalog. Check `livemode` on each returned product or price to see which catalog the call used.\n\nRequires write access to the app. Workspace viewers cannot call this operation.","operationId":"create_product_api_apps__app_id__payments_stripe_products_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductRequest"}}}},"responses":{"200":{"description":"The operation result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StripeCatalogProduct"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential is not permitted or you don't have the required app access. A missing app also fails the app access check. Setup and catalog operations also return `app_payment_management_not_allowed` when the app's workspace restricts payment management."},"404":{"description":"The app is outside the credential's granted workspace."},"409":{"description":"The workspace requires an unlocked SSO session."},"400":{"description":"Stripe keys are missing, or the app's test sandbox has expired."},"422":{"description":"The body is missing, `name` is missing, or a value doesn't match its field's type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Stripe products","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns products from the configured Stripe account.\n\nUses the Stripe account selected by the app's stored credentials. Live keys select the live catalog and test keys select the test catalog. Products and prices belong to that Stripe account, so multiple apps configured with the same account share the same catalog. Check `livemode` on each returned product or price to see which catalog the call used.\n\nReturns a single batch, with no indication that more results exist. This operation cannot retrieve a complete catalog larger than the requested limit.\n\nRequires read access to the app.","operationId":"list_products_api_apps__app_id__payments_stripe_products_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by active status. Omit to include both active and inactive records.","title":"Active"},"description":"Filter by active status. Omit to include both active and inactive records.","example":true},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"description":"Maximum number of products in this batch. Use 1 through 100. Defaults to 100. There is no cursor for another batch.","default":100,"title":"Limit"},"description":"Maximum number of products in this batch. Use 1 through 100. Defaults to 100. There is no cursor for another batch.","example":25}],"responses":{"200":{"description":"The operation result.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/StripeCatalogProduct"},"title":"Response 200 List Products Api Apps  App Id  Payments Stripe Products Get"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential is not permitted or you don't have the required app access. A missing app also fails the app access check. Setup and catalog operations also return `app_payment_management_not_allowed` when the app's workspace restricts payment management."},"404":{"description":"The app is outside the credential's granted workspace."},"409":{"description":"The workspace requires an unlocked SSO session."},"400":{"description":"Stripe keys are missing, or the app's test sandbox has expired."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/payments/stripe/products/{product_id}":{"get":{"summary":"Get Stripe product","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 product by its Stripe ID.\n\nUses the Stripe account selected by the app's stored credentials. Live keys select the live catalog and test keys select the test catalog. Products and prices belong to that Stripe account, so multiple apps configured with the same account share the same catalog. Check `livemode` on each returned product or price to see which catalog the call used.\n\nRequires read access to the app.","operationId":"get_product_api_apps__app_id__payments_stripe_products__product_id__get","parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"string","description":"Stripe product ID in the configured account.","title":"Product Id"},"description":"Stripe product ID in the configured account.","example":"prod_T7mK2p9Q4r6S8v"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The operation result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StripeCatalogProduct"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential is not permitted or you don't have the required app access. A missing app also fails the app access check. Setup and catalog operations also return `app_payment_management_not_allowed` when the app's workspace restricts payment management."},"404":{"description":"The app is outside the credential's granted workspace."},"409":{"description":"The workspace requires an unlocked SSO session."},"400":{"description":"Stripe keys are missing, or the app's test sandbox has expired."}}},"put":{"summary":"Update Stripe product","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nUpdates the product identified by `product_id`. Changes only the fields you send. Anything you leave out keeps its current value. Sending `null` has the same effect.\n\nUses the Stripe account selected by the app's stored credentials. Live keys select the live catalog and test keys select the test catalog. Products and prices belong to that Stripe account, so multiple apps configured with the same account share the same catalog. Check `livemode` on each returned product or price to see which catalog the call used.\n\nRequires write access to the app. Workspace viewers cannot call this operation.","operationId":"update_product_api_apps__app_id__payments_stripe_products__product_id__put","parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"string","description":"Stripe product ID in the configured account.","title":"Product Id"},"description":"Stripe product ID in the configured account.","example":"prod_T7mK2p9Q4r6S8v"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProductRequest"}}}},"responses":{"200":{"description":"The operation result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StripeCatalogProduct"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential is not permitted or you don't have the required app access. A missing app also fails the app access check. Setup and catalog operations also return `app_payment_management_not_allowed` when the app's workspace restricts payment management."},"404":{"description":"The app is outside the credential's granted workspace."},"409":{"description":"The workspace requires an unlocked SSO session."},"400":{"description":"Stripe keys are missing, or the app's test sandbox has expired."},"422":{"description":"A value doesn't match its field's type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/payments/stripe/prices":{"post":{"summary":"Create Stripe price","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 one-time or recurring price for the product identified by `product_id`.\n\nUses the Stripe account selected by the app's stored credentials. Live keys select the live catalog and test keys select the test catalog. Products and prices belong to that Stripe account, so multiple apps configured with the same account share the same catalog. Check `livemode` on each returned product or price to see which catalog the call used.\n\nRequires write access to the app. Workspace viewers cannot call this operation.","operationId":"create_price_api_apps__app_id__payments_stripe_prices_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePriceRequest"}}}},"responses":{"200":{"description":"The operation result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StripeCatalogPrice"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential is not permitted or you don't have the required app access. A missing app also fails the app access check. Setup and catalog operations also return `app_payment_management_not_allowed` when the app's workspace restricts payment management."},"404":{"description":"The app is outside the credential's granted workspace."},"409":{"description":"The workspace requires an unlocked SSO session."},"400":{"description":"Stripe keys are missing, or the app's test sandbox has expired."},"422":{"description":"The body is missing, `product_id` or `unit_amount` is missing, or a value doesn't match its field's type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Stripe prices","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns prices from every product in the configured Stripe account, unless narrowed to one product with `product_id`. A product can have more than one price, such as separate monthly and annual prices for the same plan.\n\nUses the Stripe account selected by the app's stored credentials. Live keys select the live catalog and test keys select the test catalog. Products and prices belong to that Stripe account, so multiple apps configured with the same account share the same catalog. Check `livemode` on each returned product or price to see which catalog the call used.\n\nReturns a single batch, with no indication that more results exist. This operation cannot retrieve a complete catalog larger than the requested limit.\n\nRequires read access to the app.","operationId":"list_prices_api_apps__app_id__payments_stripe_prices_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"product_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Stripe product ID to filter by. Defaults to no product filter.","title":"Product Id"},"description":"Stripe product ID to filter by. Defaults to no product filter.","example":"prod_T7mK2p9Q4r6S8v"},{"name":"active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by active status. Stripe defaults to active-only prices when this is omitted. Pass `false` to list inactive prices instead.","title":"Active"},"description":"Filter by active status. Stripe defaults to active-only prices when this is omitted. Pass `false` to list inactive prices instead.","example":true},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"description":"Maximum number of prices in this batch. Use 1 through 100. Defaults to 100. There is no cursor for another batch.","default":100,"title":"Limit"},"description":"Maximum number of prices in this batch. Use 1 through 100. Defaults to 100. There is no cursor for another batch.","example":25}],"responses":{"200":{"description":"The operation result.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/StripeCatalogPrice"},"title":"Response 200 List Prices Api Apps  App Id  Payments Stripe Prices Get"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential is not permitted or you don't have the required app access. A missing app also fails the app access check. Setup and catalog operations also return `app_payment_management_not_allowed` when the app's workspace restricts payment management."},"404":{"description":"The app is outside the credential's granted workspace."},"409":{"description":"The workspace requires an unlocked SSO session."},"400":{"description":"Stripe keys are missing, or the app's test sandbox has expired."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/payments/stripe/prices/{price_id}":{"put":{"summary":"Update Stripe price","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nUpdates the price identified by `price_id`. Changes only the fields you send. Anything you leave out keeps its current value. Sending `null` has the same effect. The amount, currency, and recurring schedule cannot be changed here.\n\nThe response does not include the nickname even when it was updated.\n\nUses the Stripe account selected by the app's stored credentials. Live keys select the live catalog and test keys select the test catalog. Products and prices belong to that Stripe account, so multiple apps configured with the same account share the same catalog. Check `livemode` on each returned product or price to see which catalog the call used.\n\nRequires write access to the app. Workspace viewers cannot call this operation.","operationId":"update_price_api_apps__app_id__payments_stripe_prices__price_id__put","parameters":[{"name":"price_id","in":"path","required":true,"schema":{"type":"string","description":"Stripe price ID in the configured account.","title":"Price Id"},"description":"Stripe price ID in the configured account.","example":"price_1T7mK2Q4r6S8v9Ab"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePriceRequest"}}}},"responses":{"200":{"description":"The operation result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StripeCatalogPrice"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential is not permitted or you don't have the required app access. A missing app also fails the app access check. Setup and catalog operations also return `app_payment_management_not_allowed` when the app's workspace restricts payment management."},"404":{"description":"The app is outside the credential's granted workspace."},"409":{"description":"The workspace requires an unlocked SSO session."},"400":{"description":"Stripe keys are missing, or the app's test sandbox has expired."},"422":{"description":"A value doesn't match its field's type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/payments/stripe":{"delete":{"summary":"Remove Stripe integration","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nDeletes the app's [sandbox](/developers/references/apps-api/sections/payments-stripe#from-sandbox-to-live) record and stored Stripe secrets, and clears its Stripe mode, so [Get Stripe integration status](/api-reference/get-stripe-integration-status) reports no mode.\n\nThis does not delete the Stripe account or catalog, revoke credentials at Stripe, or redeploy existing backend functions.\n\nBackend functions that were already deployed keep the credentials they were deployed with, so they can keep using Stripe until you [redeploy the app](/api-reference/deploy-an-app).\n\nRequires write access to the app. Workspace viewers cannot call this operation.","operationId":"remove_stripe_integration_api_apps__app_id__payments_stripe_delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The operation result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StripeRemovalResult"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential is not permitted or you don't have the required app access. A missing app also fails the app access check. Setup and catalog operations also return `app_payment_management_not_allowed` when the app's workspace restricts payment management."},"404":{"description":"The app is outside the credential's granted workspace."},"409":{"description":"The workspace requires an unlocked SSO session."}}}},"/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>","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, the app does not exist, or its workspace restricts payment management (`app_payment_management_not_allowed`)."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/payments/analytics/top-customers":{"get":{"summary":"Get top payment customers","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns customers ranked by live payment amount or count, highest first.\n\nCustomer details are enriched through Stripe. Amounts across currencies are summed without conversion, so filter to one currency with `currencies` before comparing them.\n\nSend `start_date` and `end_date` together to set a window. With neither, or only one of the two, `period` selects the last 7, 30, or 90 whole UTC days including today.\n\nReturns a single batch of up to `limit` records, with no cursor to retrieve the rest.\n\nResults come from an analytics store and can lag behind the payment provider.\n\nRequires read access to the app.","operationId":"get_top_customers_api_apps__app_id__payments_analytics_top_customers_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start of the window as an ISO 8601 timestamp. Send with end_date. Defaults to the period window.","title":"Start Date"},"description":"Start of the window as an ISO 8601 timestamp. Send with end_date. Defaults to the period window.","example":"2026-08-01T00:00:00Z"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End of the window as an ISO 8601 timestamp. Send with start_date. Defaults to now when using period.","title":"End Date"},"description":"End of the window as an ISO 8601 timestamp. Send with start_date. Defaults to now when using period.","example":"2026-08-31T23:59:59Z"},{"name":"period","in":"query","required":false,"schema":{"enum":["7d","30d","90d"],"type":"string","description":"Fallback window. Either `7d`, `30d`, or `90d`. Defaults to `30d`. Used unless both dates are supplied.","default":"30d","title":"Period"},"description":"Fallback window. Either `7d`, `30d`, or `90d`. Defaults to `30d`. Used unless both dates are supplied.","example":"30d"},{"name":"order_by","in":"query","required":false,"schema":{"enum":["amount","transactions"],"type":"string","description":"Ranking metric. Either `amount` or `transactions`. Defaults to `amount`. Highest values come first.","default":"amount","title":"Order By"},"description":"Ranking metric. Either `amount` or `transactions`. Defaults to `amount`. Highest values come first.","example":"amount"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":10,"minimum":1,"description":"Maximum customers to return, from 1 through 10. Defaults to 5.","default":5,"title":"Limit"},"description":"Maximum customers to return, from 1 through 10. Defaults to 5.","example":5},{"name":"currencies","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated currency codes, for example `usd,eur`. Defaults to no currency filter. Use one currency when comparing amounts.","title":"Currencies"},"description":"Comma-separated currency codes, for example `usd,eur`. Defaults to no currency filter. Use one currency when comparing amounts.","example":"usd"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopCustomersResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or its workspace restricts payment management (`app_payment_management_not_allowed`)."},"404":{"description":"The app is outside the credential grant."},"409":{"description":"The workspace requires an unlocked SSO session."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/payments/analytics/recent-transactions":{"get":{"summary":"Get recent payment transactions","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 most recent payments, refunds, and disputes, newest first.\n\nWithout a provider filter, this list spans every connected provider but only attempts customer enrichment against Stripe, so Wix customers may come back without a name or email. Filtering to one provider needs the extra access `provider` describes.\n\nSend `start_date` and `end_date` together to set a window. With neither, or only one of the two, `period` selects the last 7, 30, or 90 whole UTC days including today.\n\nReturns a single batch of up to `limit` records, with no cursor to retrieve the rest.\n\nResults come from an analytics store and can lag behind the payment provider.\n\nRequires read access to the app.","operationId":"get_recent_transactions_api_apps__app_id__payments_analytics_recent_transactions_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start of the window as an ISO 8601 timestamp. Send with end_date. Defaults to the period window.","title":"Start Date"},"description":"Start of the window as an ISO 8601 timestamp. Send with end_date. Defaults to the period window.","example":"2026-08-01T00:00:00Z"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End of the window as an ISO 8601 timestamp. Send with start_date. Defaults to now when using period.","title":"End Date"},"description":"End of the window as an ISO 8601 timestamp. Send with start_date. Defaults to now when using period.","example":"2026-08-31T23:59:59Z"},{"name":"period","in":"query","required":false,"schema":{"enum":["7d","30d","90d"],"type":"string","description":"Fallback window. Either `7d`, `30d`, or `90d`. Defaults to `30d`. Used unless both dates are supplied.","default":"30d","title":"Period"},"description":"Fallback window. Either `7d`, `30d`, or `90d`. Defaults to `30d`. Used unless both dates are supplied.","example":"30d"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":10,"minimum":1,"description":"Maximum transactions to return, from 1 through 10. Defaults to 7.","default":7,"title":"Limit"},"description":"Maximum transactions to return, from 1 through 10. Defaults to 7.","example":7},{"name":"currencies","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated currency codes, for example `usd,eur`. Defaults to no currency filter. Use one currency when comparing amounts.","title":"Currencies"},"description":"Comma-separated currency codes, for example `usd,eur`. Defaults to no currency filter. Use one currency when comparing amounts.","example":"usd"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecentTransactionsResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, the app does not exist, or its workspace restricts payment management (`app_payment_management_not_allowed`)."},"404":{"description":"The app is outside the credential grant, or a provider-scoped request names a provider that isn't connected."},"409":{"description":"The workspace requires an unlocked SSO session."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/payments/analytics/transactions/export":{"get":{"summary":"Export payment transactions","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 payments, refunds, and lost disputes for one payment provider as a CSV file, newest first.\n\nThe file starts with a header row, then has one row per transaction with these columns:\n- `Date`: The transaction time in UTC as the analytics store records it, for example `2026-08-25 10:00:00`\n- `Customer`: The customer's name or email, or the provider's customer ID when neither can be resolved\n- `Transaction ID`: The provider's ID for the transaction\n- `Status`: Either `succeeded`, `refunded`, or `failed`\n- `Amount`: In the currency's major unit, so `25.00` rather than `2500`\n- `Currency`: An uppercase three-letter ISO 4217 code\n\nCells are quoted, and cells a spreadsheet would read as a formula start with `'`.\n\nThe file covers the app's whole history unless you pass `period` or both `start_date` and `end_date`. It stops at 10,000 rows without saying so in the file, so narrow the window when a download reaches that size.\n\nCustomer names are looked up from the provider within a 20-second budget for the whole file, so a large export can leave some `Customer` cells with only an ID, or empty.\n\nThe file includes one mode per provider:\n\n- **Stripe**: Live when the app's stored key is a live key, test otherwise.\n- **Wix Payments**: Live, unless test-mode reporting is enabled for your account. Then it follows the app's Wix Payments test-mode setting.\n\nResults come from an analytics store and can lag behind the payment provider.\n\n<Note>This endpoint accepts a personal access token belonging to a user with write access to the app. A read-only personal access token is refused, and so is a viewer.</Note>","operationId":"export_transactions_csv_api_apps__app_id__payments_analytics_transactions_export_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start of the window as an ISO 8601 timestamp. Send it with `end_date`, because a date sent on its own is ignored.","title":"Start Date"},"description":"Start of the window as an ISO 8601 timestamp. Send it with `end_date`, because a date sent on its own is ignored.","example":"2026-08-01T00:00:00Z"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End of the window as an ISO 8601 timestamp. Send it with `start_date`.","title":"End Date"},"description":"End of the window as an ISO 8601 timestamp. Send it with `start_date`.","example":"2026-08-31T23:59:59Z"},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"enum":["7d","30d","90d"],"type":"string"},{"type":"null"}],"description":"Window to use when you don't send both dates, counted back over whole UTC days including today. Either `7d`, `30d`, or `90d`. Defaults to no window, which covers the app's whole history.","title":"Period"},"description":"Window to use when you don't send both dates, counted back over whole UTC days including today. Either `7d`, `30d`, or `90d`. Defaults to no window, which covers the app's whole history.","example":"30d"},{"name":"currencies","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated currency codes, for example `usd,eur`. Defaults to no currency filter.","title":"Currencies"},"description":"Comma-separated currency codes, for example `usd,eur`. Defaults to no currency filter.","example":"usd"},{"name":"provider","in":"query","required":true,"schema":{"enum":["stripe","wix"],"type":"string","description":"Payment provider whose transactions to use. Either `stripe` or `wix`. `stripe` covers payments taken through the app's Stripe integration, and `wix` covers payments taken through Wix Payments (Base44 Payments). The provider has to be connected to the app.","title":"Provider"},"description":"Payment provider whose transactions to use. Either `stripe` or `wix`. `stripe` covers payments taken through the app's Stripe integration, and `wix` covers payments taken through Wix Payments (Base44 Payments). The provider has to be connected to the app.","example":"stripe"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated statuses to include, from `succeeded`, `refunded`, and `failed`. For example, `succeeded,refunded` leaves out lost disputes. Defaults to every status.","title":"Status"},"description":"Comma-separated statuses to include, from `succeeded`, `refunded`, and `failed`. For example, `succeeded,refunded` leaves out lost disputes. Defaults to every status.","example":"succeeded,refunded"}],"responses":{"200":{"description":"The CSV file, sent as an attachment named after the app and today's date.","content":{"text/csv":{"schema":{"type":"string"},"example":"\"Date\",\"Customer\",\"Transaction ID\",\"Status\",\"Amount\",\"Currency\"\r\n\"2026-08-25 10:00:00\",\"Jane Doe\",\"pi_T7mK2p9Q4r6S8v\",\"succeeded\",\"25.00\",\"USD\"\r\n"}}},"400":{"description":"`status` has a value other than `succeeded`, `refunded`, or `failed`."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have write access to the app, it doesn't exist, or your personal access token is read-only. A missing app and an app you cannot reach are deliberately the same answer. Also returned when the app's workspace blocks payment management, with `error.code` `app_payment_management_not_allowed`."},"409":{"description":"The workspace requires an unlocked SSO session."},"404":{"description":"The app is outside the credential grant, or `provider` isn't connected to the app."},"429":{"description":"The base limit is 5 requests per hour, scoped to your account rather than the workspace. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"422":{"description":"`provider` is missing or isn't `stripe` or `wix`, or another parameter has the wrong type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/payments/analytics/transactions":{"get":{"summary":"List payment transactions","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 page of the app's payments, refunds, and lost disputes for one payment provider, newest first.\n\nUnlike [Get recent payment transactions](/api-reference/get-recent-payment-transactions), this covers the app's whole history unless you pass `period` or both `start_date` and `end_date`. Each row carries the customer's name and email when the provider can resolve them, including for a guest checkout.\n\nNew transactions are added at the top of the list, so one recorded while you're paging shifts the rest down and can cause a row to repeat at a page boundary.\n\nEach row's `is_live` reflects which mode recorded it:\n\n- **Stripe**: Live when the app's stored key is a live key, test otherwise.\n- **Wix Payments**: Live, unless test-mode reporting is enabled for your account. Then it follows the app's Wix Payments test-mode setting.\n\nResults come from an analytics store and can lag behind the payment provider.\n\n<Note>This endpoint accepts a personal access token belonging to a user with write access to the app. A read-only personal access token is refused, and so is a viewer.</Note>","operationId":"get_transactions_api_apps__app_id__payments_analytics_transactions_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start of the window as an ISO 8601 timestamp. Send it with `end_date`, because a date sent on its own is ignored.","title":"Start Date"},"description":"Start of the window as an ISO 8601 timestamp. Send it with `end_date`, because a date sent on its own is ignored.","example":"2026-08-01T00:00:00Z"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End of the window as an ISO 8601 timestamp. Send it with `start_date`.","title":"End Date"},"description":"End of the window as an ISO 8601 timestamp. Send it with `start_date`.","example":"2026-08-31T23:59:59Z"},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"enum":["7d","30d","90d"],"type":"string"},{"type":"null"}],"description":"Window to use when you don't send both dates, counted back over whole UTC days including today. Either `7d`, `30d`, or `90d`. Defaults to no window, which covers the app's whole history.","title":"Period"},"description":"Window to use when you don't send both dates, counted back over whole UTC days including today. Either `7d`, `30d`, or `90d`. Defaults to no window, which covers the app's whole history.","example":"30d"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of rows to skip, which is the number you've already read. Defaults to 0.","default":0,"title":"Offset"},"description":"Number of rows to skip, which is the number you've already read. Defaults to 0.","example":50},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Items per page. Max 100. Defaults to 50.","default":50,"title":"Limit"},"description":"Items per page. Max 100. Defaults to 50.","example":50},{"name":"currencies","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated currency codes, for example `usd,eur`. Defaults to no currency filter.","title":"Currencies"},"description":"Comma-separated currency codes, for example `usd,eur`. Defaults to no currency filter.","example":"usd"},{"name":"provider","in":"query","required":true,"schema":{"enum":["stripe","wix"],"type":"string","description":"Payment provider whose transactions to use. Either `stripe` or `wix`. `stripe` covers payments taken through the app's Stripe integration, and `wix` covers payments taken through Wix Payments (Base44 Payments). The provider has to be connected to the app.","title":"Provider"},"description":"Payment provider whose transactions to use. Either `stripe` or `wix`. `stripe` covers payments taken through the app's Stripe integration, and `wix` covers payments taken through Wix Payments (Base44 Payments). The provider has to be connected to the app.","example":"stripe"}],"responses":{"200":{"description":"One page of the app's transactions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentTransactionsResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have write access to the app, it doesn't exist, or your personal access token is read-only. A missing app and an app you cannot reach are deliberately the same answer. Also returned when the app's workspace blocks payment management, with `error.code` `app_payment_management_not_allowed`."},"409":{"description":"The workspace requires an unlocked SSO session."},"404":{"description":"The app is outside the credential grant, or `provider` isn't connected to the app."},"429":{"description":"The base limit is 120 requests per minute, scoped to your account rather than the workspace. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"422":{"description":"`provider` is missing or isn't `stripe` or `wix`, `limit` or `offset` is out of range, or another parameter has the wrong type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/payments/analytics/transactions/{transaction_id}":{"get":{"summary":"Get payment transaction","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 payment in full, read live from its payment provider.\n\nGet the required `transaction_id` from [List payment transactions](/api-reference/list-payment-transactions). Send the row's `timestamp` too when you have it, and send `action=refund` for a refund row, since Wix tracks a refund under its own ID.\n\n<Warning>The response includes the customer's personal details, such as the cardholder name, masked card number, and billing email and phone. Only a user with write access to the app can read it, and only with a personal access token set to Full access.</Warning>\n\nStripe resolves a Checkout Session, invoice, or dispute ID to the charge behind it, so `transaction_id` in the response can differ from the one you sent. Keep using the ID from the transactions list, since that's the one checked against the app.\n\n<Note>This endpoint accepts a personal access token belonging to a user with write access to the app. A read-only personal access token is refused, and so is a viewer.</Note>","operationId":"get_transaction_details_api_apps__app_id__payments_analytics_transactions__transaction_id__get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"transaction_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the transaction, taken from `transaction_id` on a row from [List payment transactions](/api-reference/list-payment-transactions).","title":"Transaction Id"},"description":"ID of the transaction, taken from `transaction_id` on a row from [List payment transactions](/api-reference/list-payment-transactions).","example":"pi_T7mK2p9Q4r6S8v"},{"name":"provider","in":"query","required":true,"schema":{"enum":["stripe","wix"],"type":"string","description":"Payment provider whose transactions to use. Either `stripe` or `wix`. `stripe` covers payments taken through the app's Stripe integration, and `wix` covers payments taken through Wix Payments (Base44 Payments). The provider has to be connected to the app.","title":"Provider"},"description":"Payment provider whose transactions to use. Either `stripe` or `wix`. `stripe` covers payments taken through the app's Stripe integration, and `wix` covers payments taken through Wix Payments (Base44 Payments). The provider has to be connected to the app.","example":"stripe"},{"name":"timestamp","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"The row's own `timestamp` from [List payment transactions](/api-reference/list-payment-transactions), which makes the lookup faster. Leave it out rather than guess, since a wrong value can make the transaction look like it isn't the app's.","title":"Timestamp"},"description":"The row's own `timestamp` from [List payment transactions](/api-reference/list-payment-transactions), which makes the lookup faster. Leave it out rather than guess, since a wrong value can make the transaction look like it isn't the app's.","example":"2026-08-25T10:00:00Z"},{"name":"action","in":"query","required":false,"schema":{"anyOf":[{"enum":["payment","refund"],"type":"string"},{"type":"null"}],"description":"The row's own `action` from the transactions list. Either `payment` or `refund`. Send `refund` for a refund row so the payment it refunded is returned. Defaults to `payment`.","title":"Action"},"description":"The row's own `action` from the transactions list. Either `payment` or `refund`. Send `refund` for a refund row so the payment it refunded is returned. Defaults to `payment`.","example":"payment"}],"responses":{"200":{"description":"The payment, in full.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionDetails"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have write access to the app, it doesn't exist, or your personal access token is read-only. A missing app and an app you cannot reach are deliberately the same answer. Also returned when the app's workspace blocks payment management, with `error.code` `app_payment_management_not_allowed`."},"409":{"description":"The workspace requires an unlocked SSO session."},"404":{"description":"The app is outside the credential grant, or `provider` isn't connected to the app. Also returned with `error.code` `transaction_not_found` when the transaction isn't one of the app's in the current mode, or the provider doesn't have it."},"429":{"description":"The base limit is 60 requests per minute, scoped to your account rather than the workspace. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"422":{"description":"`provider` is missing or isn't `stripe` or `wix`, `action` isn't `payment` or `refund`, or `timestamp` isn't a valid timestamp.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/payments/analytics/transactions/{transaction_id}/refund":{"post":{"summary":"Refund payment transaction","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nRefunds all or part of a payment through its payment provider, then returns the payment's details after the refund.\n\n<Warning>In live mode this sends real money back to the customer, and a refund can't be undone.</Warning>\n\nRefunding less than the full amount is a partial refund, and you can refund again later up to what's left.\n\nSend an idempotency key that stays the same across retries of one refund attempt, and a new one for each new refund. Put it in the `Idempotency-Key` header or in `idempotency_key` in the body. A retry with the same key after the refund went through returns the payment without refunding again, and a retry with the same key but a different amount is rejected. This part is the same for both providers.\n\nProviders only differ when a refund attempt's outcome is unclear, for example after a timeout:\n\n- **Stripe**: Your idempotency key is also passed to Stripe as its own, so retrying with the same key is safe either way.\n- **Wix Payments**: Has no idempotency key of its own, so Base44 can't tell whether a retry would double-refund. An unclear outcome pins the key, so retrying with it never succeeds, even much later. Check `refunds` with [Get payment transaction](/api-reference/get-payment-transaction) to see whether the refund went through, then use a new key to try again.\n\nEach refund is recorded in the workspace's audit log as a `payments.transaction.refunded` [event](/developers/references/audit-logs-api/get-started/event-types).\n\n<Note>This endpoint accepts a personal access token belonging to a user with write access to the app. A read-only personal access token is refused, and so is a viewer.</Note>","operationId":"refund_transaction_api_apps__app_id__payments_analytics_transactions__transaction_id__refund_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"transaction_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the transaction, taken from `transaction_id` on a row from [List payment transactions](/api-reference/list-payment-transactions).","title":"Transaction Id"},"description":"ID of the transaction, taken from `transaction_id` on a row from [List payment transactions](/api-reference/list-payment-transactions).","example":"pi_T7mK2p9Q4r6S8v"},{"name":"provider","in":"query","required":true,"schema":{"enum":["stripe","wix"],"type":"string","description":"Payment provider whose transactions to use. Either `stripe` or `wix`. `stripe` covers payments taken through the app's Stripe integration, and `wix` covers payments taken through Wix Payments (Base44 Payments). The provider has to be connected to the app.","title":"Provider"},"description":"Payment provider whose transactions to use. Either `stripe` or `wix`. `stripe` covers payments taken through the app's Stripe integration, and `wix` covers payments taken through Wix Payments (Base44 Payments). The provider has to be connected to the app.","example":"stripe"},{"name":"timestamp","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"The row's own `timestamp` from [List payment transactions](/api-reference/list-payment-transactions), which makes the lookup faster. Leave it out rather than guess, since a wrong value can make the transaction look like it isn't the app's.","title":"Timestamp"},"description":"The row's own `timestamp` from [List payment transactions](/api-reference/list-payment-transactions), which makes the lookup faster. Leave it out rather than guess, since a wrong value can make the transaction look like it isn't the app's.","example":"2026-08-25T10:00:00Z"},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":100},{"type":"null"}],"description":"Key for this refund attempt, the same as `idempotency_key` in the body. Send it in either place, or in both with the same value.","title":"Idempotency-Key"},"description":"Key for this refund attempt, the same as `idempotency_key` in the body. Send it in either place, or in both with the same value.","example":"refund-2026-08-25-7f3c9a"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundTransactionRequest"}}}},"responses":{"200":{"description":"The provider accepted the refund. The body is the payment after it, or the payment from before it when the details can't be read again right away.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionDetails"}}}},"400":{"description":"The provider rejected the refund, for example because `amount` is above the refundable balance or the payment is disputed. `error.code` is `refund_rejected`. For Stripe, `error.details.reason` is `amount_exceeds_refundable_balance`, `already_refunded`, `charge_disputed`, `provider_not_configured`, or `rejected_by_provider`. Also returned with `error.code` `idempotency_key_required` when no idempotency key is sent, and `idempotency_key_mismatch` when the header and the body carry different ones."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have write access to the app, it doesn't exist, or your personal access token is read-only. A missing app and an app you cannot reach are deliberately the same answer."},"409":{"description":"A refund with this `idempotency_key` is still being processed (`refund_in_progress`), or it already went through with a different amount (`refund_amount_mismatch`). Also returned when the workspace requires an unlocked SSO session."},"404":{"description":"The app is outside the credential grant, or `provider` isn't connected to the app. Also returned with `error.code` `transaction_not_found` when the transaction isn't one of the app's in the current mode, or the provider doesn't have it."},"422":{"description":"`amount` isn't a positive integer, an idempotency key is empty or longer than 100 characters, `note` is longer than 500 characters, the body has other fields, or `provider` is missing or isn't `stripe` or `wix`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"The base limit is 30 requests per hour, scoped to your account rather than the workspace. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."},"503":{"description":"Refunds are temporarily unavailable. `error.code` is `refund_temporarily_unavailable`. Retry later; no refund was attempted."}}}},"/api/payment-webhooks/stripe":{"post":{"summary":"Receive Stripe sandbox event","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\n<Note>Base44 registers this endpoint with the payment provider for you, and the provider is its only caller. You can't invoke it for your own app or repoint it. It's documented so you can recognize the traffic and read the acknowledgements it returns.</Note>\n\nReceives Stripe events for apps connected to a Stripe sandbox, and for the sandbox lifecycle itself.\n\nBase44 verifies the `Stripe-Signature` header against its sandbox signing secret before reading the body, and rejects the request when the header is missing or the signature doesn't match. Live-mode events go to [Receive Stripe live event](/api-reference/receive-stripe-live-event), which verifies against a separate secret.\n\nVerified events update the app's Stripe sandbox state and record revenue for payment analytics. Checkout sessions, payment intents, invoices and charges are tracked or acknowledged, and any other event type comes back as `ignored`. A verified event Base44 can't process returns an error instead of an acknowledgement, and Stripe retries the delivery, so the acknowledgement is what tells you the event landed.\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":"stripe_webhook_api_payment_webhooks_stripe_post","parameters":[{"name":"Stripe-Signature","in":"header","required":false,"schema":{"type":"string","description":"Required on every request. Stripe's signature over the raw request body. A request that omits it is rejected with a 400, as is one whose signature doesn't verify.","title":"Stripe-Signature"},"description":"Required on every request. Stripe's signature over the raw request body. A request that omits it is rejected with a 400, as is one whose signature doesn't verify.","example":"t=1730902800,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd"}],"responses":{"200":{"description":"How Base44 handled the event.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentWebhookAck"}}}},"400":{"description":"The `Stripe-Signature` header is missing, or the signature doesn't match."}},"security":[]}},"/api/payment-webhooks/stripe/live":{"post":{"summary":"Receive Stripe live event","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\n<Note>Base44 registers this endpoint with the payment provider for you, and the provider is its only caller. You can't invoke it for your own app or repoint it. It's documented so you can recognize the traffic and read the acknowledgements it returns.</Note>\n\nReceives live-mode Stripe events for apps that have gone live with their own Stripe account.\n\nThis endpoint behaves exactly like [Receive Stripe sandbox event](/api-reference/receive-stripe-sandbox-event) and returns the same acknowledgements. It exists separately because live events are signed with a different secret, so Base44 verifies the `Stripe-Signature` header against its live signing secret and rejects anything that doesn't match 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":"stripe_live_webhook_api_payment_webhooks_stripe_live_post","parameters":[{"name":"Stripe-Signature","in":"header","required":false,"schema":{"type":"string","description":"Required on every request. Stripe's signature over the raw request body. A request that omits it is rejected with a 400, as is one whose signature doesn't verify.","title":"Stripe-Signature"},"description":"Required on every request. Stripe's signature over the raw request body. A request that omits it is rejected with a 400, as is one whose signature doesn't verify.","example":"t=1730902800,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd"}],"responses":{"200":{"description":"How Base44 handled the event.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentWebhookAck"}}}},"400":{"description":"The `Stripe-Signature` header is missing, or the signature doesn't match."}},"security":[]}},"/api/apps/{app_id}/payments/wix/dashboard-url":{"get":{"summary":"Get Wix Payments dashboard link","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 one-time link to the app's Wix Payments dashboard.\n\nThis endpoint depends on the app's [Wix site connection](/developers/references/apps-api/sections/payments-wix#wix-site-connection) rather than on Wix Payments being actively connected, so it can still return a working link after Wix Payments is disconnected.","operationId":"get_dashboard_url_api_apps__app_id__payments_wix_dashboard_url_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"redirect_url","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional redirect on the same origin as the request. Invalid destinations are ignored. Takes priority over `destination` and `open_assistant`.","title":"Redirect Url"},"description":"Optional redirect on the same origin as the request. Invalid destinations are ignored. Takes priority over `destination` and `open_assistant`.","example":"/apps"},{"name":"open_assistant","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to open the payment methods assistant, when neither `redirect_url` nor a named `destination` applies. Defaults to `false`.","default":false,"title":"Open Assistant"},"description":"Whether to open the payment methods assistant, when neither `redirect_url` nor a named `destination` applies. Defaults to `false`.","example":false},{"name":"destination","in":"query","required":false,"schema":{"$ref":"#/components/schemas/WixDashboardDestination","description":"Which Wix Payments page to open: `dashboard`, `payment_settings`, or `payouts`. Defaults to `dashboard`. Takes priority over `open_assistant`, but `redirect_url` takes priority over both.","default":"dashboard"},"description":"Which Wix Payments page to open: `dashboard`, `payment_settings`, or `payouts`. Defaults to `dashboard`. Takes priority over `open_assistant`, but `redirect_url` takes priority over both.","example":"dashboard"},{"name":"prefetch","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to only mint the link, for a caller that may never open it. A prefetching caller confirms an actual open through the commit route. Defaults to false.","default":false,"title":"Prefetch"},"description":"Whether to only mint the link, for a caller that may never open it. A prefetching caller confirms an actual open through the commit route. Defaults to false.","example":false}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DashboardUrlResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential lacks app write access, or the app's workspace restricts payment management (`app_payment_management_not_allowed`)."},"409":{"description":"The workspace requires an unlocked SSO session."},"404":{"description":"The app has no connected Wix site, or it is outside the credential's granted workspace."},"422":{"description":"`open_assistant` isn't a valid boolean, or `destination` isn't a recognized value.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/payments/wix/test-mode":{"put":{"summary":"Switch Wix Payments test mode","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSwitches the app's Wix Payments checkout between test mode and live.\n\n<Warning>Turning test mode on stops real customers from paying until the app is switched back to live.</Warning>\n\nWhile test mode is on, the checkout takes test payments and real cards aren't charged. Verify a test purchase, then send `test_mode: false` to start taking real payments. The mode is a Wix Payments setting, so the switch applies to the published app without a deploy.\n\nSending the mode the app is already in changes nothing and still returns it.","operationId":"update_test_mode_api_apps__app_id__payments_wix_test_mode_put","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTestModeRequest"}}}},"responses":{"200":{"description":"The app's Wix Payments mode after the call.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestModeResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential lacks app write access, or the app's workspace restricts payment management (`app_payment_management_not_allowed`)."},"409":{"description":"The workspace requires an unlocked SSO session."},"400":{"description":"Wix Payments isn't connected for the app, or its credentials are missing. Reconnect Wix Payments."},"404":{"description":"The app isn't found, or it's outside the credential's granted workspace."},"422":{"description":"`test_mode` is missing or isn't a boolean, or the body has other fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"The base limit is 30 requests per hour, scoped to your account rather than the workspace. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/payments/wix":{"delete":{"summary":"Disconnect Wix Payments","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 payment integration and removes its stored provider credentials.\n\nWix Payments and Payments by Wix share one set of live checkout credentials. Disconnecting repoints those credentials to the other integration if it's still connected, or clears them if not, so the deployed checkout can no longer create new charges. This takes effect immediately, without a separate deploy.\n\nThis does not delete the Wix account, and preserves the [Wix site connection](/developers/references/apps-api/sections/payments-wix#wix-site-connection), so dashboard access keeps working. An in-progress checkout can still complete.\n\nRequires write access to the app.","operationId":"disconnect_wix_payments_api_apps__app_id__payments_wix_delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The integration was disconnected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WixPaymentDisconnectResult"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential is not permitted or you don't have write access to the app. A missing app also fails the app access check."},"409":{"description":"The workspace requires an unlocked SSO session."},"400":{"description":"The integration is not connected — either it was never connected, or a previous call already disconnected it."},"404":{"description":"The app is outside the credential's granted workspace."}}}},"/api/apps/{app_id}/payments/wix/payouts":{"get":{"summary":"List Wix Payments payouts","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 Wix Payments payouts, newest first, along with the funds scheduled to be paid out and the funds on hold.\n\nThe data is read live from Wix Payments. The first page also reads the account balance: `scheduled` and `on_hold` are filled in only there, and `balance_unavailable` is `true` when Wix couldn't return the balance while the payouts themselves still came back.\n\nPass `next_cursor` back as `cursor` to get the next page, and leave `limit` out when you do, since the cursor keeps the page size you started with. Every page covers only what was created before the first page was read, so a new one doesn't shift the pages you're walking. A cursor expires after 24 hours.\n\nThis endpoint is limited to 60 requests per minute, scoped to your account rather than the workspace.\n\nTo open the payouts page of the Wix Payments dashboard instead, call [Get Wix Payments dashboard link](/api-reference/get-wix-payments-dashboard-link) with `destination` set to `payouts`.\n\n<Note>This endpoint only reads, but it needs write access to the app and a personal access token set to Full access. A viewer is refused, and so is a read-only token. The app's workspace also has to allow payment management.</Note>","operationId":"get_app_payouts_api_apps__app_id__payments_wix_payouts_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`next_cursor` from the previous page. Leave it out to get the first page.","title":"Cursor"},"description":"`next_cursor` from the previous page. Leave it out to get the first page.","example":"gAAAAABn7vJ2kXq9..."},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":100,"minimum":1},{"type":"null"}],"description":"Payouts per page, from 1 to 100. Defaults to 20. Send it only on the first page.","title":"Limit"},"description":"Payouts per page, from 1 to 100. Defaults to 20. Send it only on the first page.","example":20}],"responses":{"200":{"description":"One page of the app's payouts, with its scheduled and held funds on the first page.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayoutList"}}}},"400":{"description":"`cursor` is malformed, expired, or from another app, or `limit` was sent with it (`invalid_request`). Also returned when the cursor was issued for a different payout or Wix site (`invalid_cursor`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"403":{"description":"You don't have write access to the app, or your personal access token is read-only (`forbidden`). Also returned when the app's workspace doesn't allow payment management (`app_payment_management_not_allowed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"409":{"description":"The workspace requires an unlocked SSO session.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"422":{"description":"`limit` isn't a whole number from 1 to 100 (`invalid_request`). `error.details.errors` lists each problem.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"429":{"description":"Rate limit reached (`rate_limited`). The base limit is 60 requests per minute, scoped to your account rather than the workspace. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"404":{"description":"Wix Payments isn't connected to the app (`wix_payments_not_connected`), or the app is outside the credential's grant (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}}}}},"/api/apps/{app_id}/payments/wix/payouts/{payout_id}":{"get":{"summary":"Get Wix Payments payout","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 Wix Payments payout and the balance records it pays out, newest first.\n\nThe data is read live from Wix Payments. `summary_items` totals the whole payout by record type and is filled in only on the first page.\n\nPass `next_cursor` back as `cursor` to get the next page, and leave `limit` out when you do, since the cursor keeps the page size you started with. Every page covers only what was created before the first page was read, so a new one doesn't shift the pages you're walking. A cursor expires after 24 hours.\n\nThis endpoint is limited to 60 requests per minute, scoped to your account rather than the workspace.\n\n<Note>This endpoint only reads, but it needs write access to the app and a personal access token set to Full access. A viewer is refused, and so is a read-only token. The app's workspace also has to allow payment management.</Note>","operationId":"get_app_payout_detail_api_apps__app_id__payments_wix_payouts__payout_id__get","parameters":[{"name":"payout_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the payout, taken from `id` on a payout from [List Wix Payments payouts](/api-reference/list-wix-payments-payouts).","title":"Payout Id"},"description":"ID of the payout, taken from `id` on a payout from [List Wix Payments payouts](/api-reference/list-wix-payments-payouts).","example":"7c1a3e52-4b0d-4f7e-9a61-2d8f5c3b9e10"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`next_cursor` from the previous page of records. Leave it out to get the first page.","title":"Cursor"},"description":"`next_cursor` from the previous page of records. Leave it out to get the first page.","example":"gAAAAABn7vJ2kXq9..."},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":100,"minimum":1},{"type":"null"}],"description":"Balance records per page, from 1 to 100. Defaults to 50. Send it only on the first page.","title":"Limit"},"description":"Balance records per page, from 1 to 100. Defaults to 50. Send it only on the first page.","example":50}],"responses":{"200":{"description":"The payout and one page of its balance records.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayoutDetail"}}}},"400":{"description":"`cursor` is malformed, expired, or from another app, or `limit` was sent with it (`invalid_request`). Also returned when the cursor was issued for a different payout or Wix site (`invalid_cursor`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"403":{"description":"You don't have write access to the app, or your personal access token is read-only (`forbidden`). Also returned when the app's workspace doesn't allow payment management (`app_payment_management_not_allowed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"409":{"description":"The workspace requires an unlocked SSO session.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"422":{"description":"`limit` isn't a whole number from 1 to 100 (`invalid_request`). `error.details.errors` lists each problem.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"429":{"description":"Rate limit reached (`rate_limited`). The base limit is 60 requests per minute, scoped to your account rather than the workspace. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}},"404":{"description":"The payout isn't one of the app's Wix Payments payouts (`payout_not_found`), Wix Payments isn't connected to the app (`wix_payments_not_connected`), or the app is outside the credential's grant (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardErrorEnvelope"}}}}}}},"/api/payments/wix/resolve-name":{"get":{"summary":"Resolve Wix Payments onboarding name","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nLooks up the authenticated caller's first and last name from data Base44 already has, so [Wix Payments onboarding](/developers/references/apps-api/sections/payments-wix#onboarding-name) doesn't have to ask the caller to type a name in. Checks the caller's saved profile first, then the billing name on their personal workspace.\n\nThe two names are returned together or not at all. A stored first name with no last name, or the reverse, counts as not found, with `needs_full_name` `true` and both name fields `null`.","operationId":"resolve_name_endpoint_api_payments_wix_resolve_name_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolveNameResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential is not permitted on this route."},"409":{"description":"The workspace requires an unlocked SSO session."}}}},"/api/payment-webhooks/wix-payments":{"post":{"summary":"Receive Wix Payments event","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\n<Note>Base44 registers this endpoint with the payment provider for you, and the provider is its only caller. You can't invoke it for your own app or repoint it. It's documented so you can recognize the traffic and read the acknowledgements it returns.</Note>\n\nReceives Wix Payments and Payments by Wix transaction and refund events for a connected app.\n\nBase44 verifies the `Digest` header as an HMAC-SHA256 of the raw body before reading it, and rejects the request when the header is missing, the signature doesn't match, or the body isn't valid JSON.\n\nVerified events record revenue and refunds for payment analytics. Base44 routes the event by the `base44AppId` the provider echoes back, so an event without one comes back as `ignored`. Only a transaction that becomes `APPROVED` for the first time, for a non-zero amount, is recorded; anything else is acknowledged without being tracked. A verified event Base44 can't process returns an error instead of an acknowledgement, and the provider retries the delivery.\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":"wix_payments_webhook_api_payment_webhooks_wix_payments_post","parameters":[{"name":"Digest","in":"header","required":false,"schema":{"type":"string","description":"Required on every request. HMAC-SHA256 of the raw request body, hex-encoded. A request that omits it is rejected with a 400, as is one whose signature doesn't verify.","title":"Digest"},"description":"Required on every request. HMAC-SHA256 of the raw request body, hex-encoded. A request that omits it is rejected with a 400, as is one whose signature doesn't verify.","example":"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"}],"responses":{"200":{"description":"How Base44 handled the event.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentWebhookAck"}}}},"400":{"description":"The `Digest` header is missing, the signature doesn't match, or the body isn't valid JSON."}},"security":[]}},"/api/apps/{app_id}/payments/payments-by-wix/dashboard-url":{"get":{"summary":"Get Payments by Wix dashboard link","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nLegacy integration for existing connections only. New installations are not supported.\n\nReturns a one-time link to the app's Payments by Wix dashboard.\n\nThis endpoint depends on the app's [Wix site connection](/developers/references/apps-api/sections/payments-wix#wix-site-connection) rather than on Payments by Wix being actively connected, so it can still return a working link after Payments by Wix is disconnected.","operationId":"get_dashboard_url_api_apps__app_id__payments_payments_by_wix_dashboard_url_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"redirect_url","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional redirect on the same origin as the request. Invalid destinations are ignored. Takes priority over `open_assistant`.","title":"Redirect Url"},"description":"Optional redirect on the same origin as the request. Invalid destinations are ignored. Takes priority over `open_assistant`.","example":"/apps"},{"name":"open_assistant","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to open the payment methods assistant, when `redirect_url` isn't set. Defaults to `false`.","default":false,"title":"Open Assistant"},"description":"Whether to open the payment methods assistant, when `redirect_url` isn't set. Defaults to `false`.","example":false}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DashboardUrlResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential lacks app write access, or the app's workspace restricts payment management (`app_payment_management_not_allowed`)."},"409":{"description":"The workspace requires an unlocked SSO session."},"404":{"description":"The app has no connected Wix site, or it is outside the credential's granted workspace."},"422":{"description":"`open_assistant` isn't a valid boolean.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/{app_id}/payments/payments-by-wix":{"delete":{"summary":"Disconnect Payments by Wix","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nLegacy integration for existing connections only. New installations are not supported.\n\nDisconnects the payment integration and removes its stored provider credentials.\n\nWix Payments and Payments by Wix share one set of live checkout credentials. Disconnecting repoints those credentials to the other integration if it's still connected, or clears them if not, so the deployed checkout can no longer create new charges. This takes effect immediately, without a separate deploy.\n\nThis does not delete the Wix account, and preserves the [Wix site connection](/developers/references/apps-api/sections/payments-wix#wix-site-connection), so dashboard access keeps working. An in-progress checkout can still complete.\n\nRequires write access to the app.","operationId":"disconnect_payments_by_wix_api_apps__app_id__payments_payments_by_wix_delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Base44 app.","title":"App Id"},"description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The integration was disconnected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WixPaymentDisconnectResult"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"The credential is not permitted or you don't have write access to the app. A missing app also fails the app access check."},"409":{"description":"The workspace requires an unlocked SSO session."},"400":{"description":"The integration is not connected — either it was never connected, or a previous call already disconnected it."},"404":{"description":"The app is outside the credential's granted workspace."}}}},"/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/workspace-installations":{"get":{"summary":"List workspace GitHub installations","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 organizations connected to the app's workspace that you can create the app's repository in. Pass an installation's `id` as `workspace_installation_id` to [Connect a GitHub repository](/api-reference/connect-a-github-repository).\n\nOnly active installations on GitHub organizations appear, and only those the workspace allows for new apps. Organizations are connected to the workspace in its settings rather than through this API. Calling this re-checks `repository_selection` with GitHub for installations set to `selected`.\n\nThis endpoint is limited to 30 requests per minute. Some workspaces have a different limit.\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_app_workspace_installations_api_apps__app_id__github_workspace_installations_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 workspace's GitHub installations you can use.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GitHubWorkspaceInstallations"}}}},"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."},"429":{"description":"Rate limit exceeded. The base limit is 30 requests per minute. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/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 until you [disconnect it](/api-reference/disconnect-a-github-repository).\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 both report `connected: false`. After a user disconnects the app, `previous_repository` names the repository [Reconnect a GitHub repository](/api-reference/reconnect-a-github-repository) can bring back.","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/disconnect":{"delete":{"summary":"Disconnect 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\nDisconnects the app from its GitHub repository. The repository itself stays on GitHub, untouched.\n\nBase44 first copies the repository's current code into its own storage, along with the app's open [branches](/developers/references/app-management/get-started/concepts#branches), and the app carries on from that version. If the copy fails, the app stays connected and you can retry. Base44 then stops syncing and tries to remove the webhook it installed on the repository. A webhook it can't remove no longer affects the app. The response comes back only after all of that finishes.\n\nAfterwards [Get GitHub connection](/api-reference/get-github-connection) reports `connected: false` and names the repository in `previous_repository`. Restore the connection with [Reconnect a GitHub repository](/api-reference/reconnect-a-github-repository). Connecting a different repository with [Connect a GitHub repository](/api-reference/connect-a-github-repository) replaces the previous one, which the app can then no longer reconnect to.","operationId":"disconnect_repository_api_apps__app_id__github_disconnect_delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to disconnect from its GitHub repository.","title":"App Id"},"description":"ID of the app to disconnect from its GitHub repository.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The app was disconnected from its repository.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisconnectResponse"}}}},"400":{"description":"The app has no active GitHub connection."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The app's workspace plan doesn't include the GitHub integration."},"403":{"description":"You don't have access to this app, you're neither its owner nor the user who connected the repository, you used a read-only personal access token, 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."}}}},"/api/apps/{app_id}/github/reconnect":{"post":{"summary":"Reconnect 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\nReconnects the app to the GitHub repository it was disconnected from.\n\nReconnecting works only for an app that a user disconnected. Check `previous_repository` in [Get GitHub connection](/api-reference/get-github-connection) first to see whether the app has a repository to reconnect to.\n\nBase44 compares the app's history with the repository's default branch and brings whichever side is behind up to date:\n- When they match, nothing moves.\n- When the app has newer commits, Base44 pushes them to the repository.\n- When the repository has newer commits, Base44 pulls them into the app.\n\nWhen both sides have new commits, or their histories are unrelated, the reconnect is refused and the app stays disconnected. A successful reconnect also turns automatic sync back on, copies the app's open [branches](/developers/references/app-management/get-started/concepts#branches) to the repository, and installs a webhook that tells Base44 about new commits.\n\nInstalling that webhook can fail without failing the reconnect, so check `webhook_active` in [Get GitHub connection](/api-reference/get-github-connection) afterwards. If you lost the response, check [Get GitHub connection](/api-reference/get-github-connection) before you retry. A reconnect that already succeeded answers a retry with `no_previous_repository`.\n\nIf the pull from the repository fails, the reconnect still succeeds and the failure is reported in `sync`. Retry the pull with [Pull changes from GitHub](/api-reference/pull-changes-from-github).\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":"reconnect_repository_api_apps__app_id__github_reconnect_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app to reconnect to the GitHub repository it was disconnected from.","title":"App Id"},"description":"ID of the app to reconnect to the GitHub repository it was disconnected from.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The app is connected to its repository again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GitHubReconnectResult"}}}},"400":{"description":"The app has nothing to reconnect to, because it's connected already, a user never disconnected it, or its repository was deleted on GitHub. The `code` is `no_previous_repository`."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"The app's workspace plan doesn't include the GitHub integration. The `code` is `capability_required`."},"403":{"description":"You don't have access to this app, you used a read-only personal access token, or you used a workspace API key. The other refusals carry a `code`:\n- `owner_required` when you aren't the app's owner.\n- `installation_unavailable` or `push_access_missing` when the Base44 GitHub App can't write to the repository any more.\n- `github_org_not_allowed` when the app's workspace doesn't approve the repository's GitHub organization."},"404":{"description":"App not found, or the repository no longer exists under the name it was connected as. Only the second carries a `code`, which is `repository_not_found`."},"409":{"description":"The reconnect was refused, and `code` says why:\n- `histories_diverged` when both the app and the repository have new commits since they last matched.\n- `unrelated_histories` when the repository's history doesn't share a starting point with the app's.\n- `repository_moved` when the repository was renamed or transferred. `details` holds the old and new names.\n- `default_branch_changed` when the repository's default branch changed. `details` holds both branch names.\n- `workspace_mismatch` when the app moved to a different workspace after it was connected.\n- `workspace_installation_inactive` when the workspace's GitHub installation was disconnected.\n- `app_already_processing` or `branch_turn_in_progress` when the app or one of its branches is busy.\n- `branch_refs_rejected` when an open branch could not be copied, including a missing source branch or a push rejected by GitHub."},"502":{"description":"The reconnect failed on GitHub's side. Nothing was activated, so you can retry. The `code` says why:\n- `github_unavailable` when GitHub couldn't be reached.\n- `history_unavailable` when Base44 couldn't read the history it needs to compare.\n- `push_failed` when GitHub didn't accept the app's commits."}}}},"/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/apps-api/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 AI 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 an AI [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. 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 AI 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/apps-api/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, whether or not [automatic sync](/api-reference/turn-automatic-github-sync-on-or-off) is on. Use this when a push failed.\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."}}}},"/api/apps/{app_id}/github/sync/toggle":{"post":{"summary":"Turn automatic GitHub sync on or off","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nTurns automatic sync between the app and its connected GitHub repository on or off.\n\nAutomatic sync brings in commits pushed to the repository from outside Base44. While it's on, the Base44 editor pulls them in as soon as GitHub reports them, as long as someone has the app open in it. While it's off, Base44 ignores those reports, so new commits come in only when someone opens the app in the editor or you call [Pull changes from GitHub](/api-reference/pull-changes-from-github). The setting covers the app's main line and its [branches](/developers/references/app-management/get-started/concepts#branches) alike.\n\nThe setting doesn't affect the other direction. Base44 pushes every saved version to the repository whether automatic sync is on or off, and [Push changes to GitHub](/api-reference/push-changes-to-github) works either way.\n\nWhen the repository receives more than 20 pushes within a minute, counting Base44's own, Base44 pauses automatic sync for an hour. Pushes during the pause are ignored, even if you turn automatic sync on, and turning it on doesn't end the pause early. After the hour, the next push ends the pause. Base44 then turns automatic sync back on only if the pause is what turned automatic sync off.\n\n[Reconnect a GitHub repository](/api-reference/reconnect-a-github-repository) also turns automatic sync back on.","operationId":"toggle_sync_api_apps__app_id__github_sync_toggle_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"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToggleSyncRequest"}}}},"responses":{"200":{"description":"The setting was saved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToggleSyncResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have editor access to this app, you used a read-only personal access token, or you used a workspace API key."},"404":{"description":"App not found, or the app has no active GitHub connection."},"422":{"description":"The request body is missing a required field, or one of its values has the wrong type."},"429":{"description":"Rate limit exceeded. The base limit is 10 requests per minute for each app, and every personal access token in a workspace shares it. See [Rate limits](/developers/references/apps-api/get-started/rate-limits) for the multiplier your plan gets."}}}},"/api/apps/{app_id}/agent-configs/setup-suggestions":{"get":{"summary":"Get agent setup 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 up to 3 agents worth adding to the app, based on its name, description, data entities, and the agents it already has. They're the suggestions the builder shows on the app's Agents page.\n\nEach suggestion carries a `prompt` you can send to the app's AI chat to build that agent. Nothing is created by this call.\n\nSuggestions are generated by AI and kept for 24 hours for each app and `page`, so changes to the app show up in them after that. Generating them doesn't use the workspace's credits.\n\nThis is limited to 10 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a full-access personal API key from anyone with access to the app, viewers included. Read-only keys and workspace API keys are not authorized for it, because a call can start AI generation.</Note>","operationId":"get_setup_suggestions_api_apps__app_id__agent_configs_setup_suggestions_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose agents to read.","title":"App Id"},"description":"ID of the app whose agents to read.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","maximum":1,"minimum":0,"description":"Which set of suggestions to return. `0` covers the assistants users expect right away, and `1` covers agents that keep users engaged and follow up with them. Defaults to `0`.","default":0,"title":"Page"},"description":"Which set of suggestions to return. `0` covers the assistants users expect right away, and `1` covers agents that keep users engaged and follow up with them. Defaults to `0`.","example":1}],"responses":{"200":{"description":"Suggested agents for the app.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentSetupSuggestionsResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, your API key is read-only, or you used a workspace API key."},"404":{"description":"App not found."},"422":{"description":"`page` isn't `0` or `1`."},"429":{"description":"Too many requests for this app in the last minute from personal API keys in your workspace."}}}},"/api/apps/{app_id}/agent-configs/conversation-users":{"get":{"summary":"List agent conversation 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 the app's users who have conversations with its agents, most recently active first. Each user comes with totals, a breakdown by agent, and up to 100 of their conversations.\n\nThe response carries personal data about the app's users. That includes their emails and names, and the start and end of their messages.\n\nYou can filter by:\n- Text in the user's email or name\n- Agent\n- App role\n- Recent activity\n\nResults use offset pagination. Page with `limit` and `skip`, and stop once you've read `total` users. Open a conversation's messages with [Get agent conversation](/api-reference/get-agent-conversation).\n\nThis is limited to 60 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key, including a read-only one, from anyone with access to the app, viewers included. Workspace API keys are not authorized for it.</Note>","operationId":"list_conversation_users_api_apps__app_id__agent_configs_conversation_users_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose agents to read.","title":"App Id"},"description":"ID of the app whose agents to read.","example":"6820f3a4e7b91d003c45a1f2"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Text to match, ignoring case, against the app user's email or full name, the agent's name, or a WhatsApp phone number. Only the first 200 characters are used.","title":"Search"},"description":"Text to match, ignoring case, against the app user's email or full name, the agent's name, or a WhatsApp phone number. Only the first 200 characters are used.","example":"jane"},{"name":"owner_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Whose conversations to include. Either `all`, `mine` for only the ones you started as a user of the app, or `others` for everyone else's. Defaults to `all`.","title":"Owner Filter"},"description":"Whose conversations to include. Either `all`, `mine` for only the ones you started as a user of the app, or `others` for everyone else's. Defaults to `all`.","example":"others"},{"name":"agent_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Names of the agents to include, as a comma-separated list or a JSON array. For example, `support_agent,sales_agent`. Leave it out for every agent.","title":"Agent Filter"},"description":"Names of the agents to include, as a comma-separated list or a JSON array. For example, `support_agent,sales_agent`. Leave it out for every agent.","example":"support_agent"},{"name":"time_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Keep only conversations updated since midnight UTC (`today`), in the last 7 days (`7d`), or in the last 30 days (`30d`). Defaults to `all`.","title":"Time Filter"},"description":"Keep only conversations updated since midnight UTC (`today`), in the last 7 days (`7d`), or in the last 30 days (`30d`). Defaults to `all`.","example":"7d"},{"name":"role_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"App roles to include, as a comma-separated list or a JSON array of `user`, `admin`, and `editor`. For example, `user,admin`. Anonymous visitors are left out when you set it. Defaults to `all`.","title":"Role Filter"},"description":"App roles to include, as a comma-separated list or a JSON array of `user`, `admin`, and `editor`. For example, `user,admin`. Anonymous visitors are left out when you set it. Defaults to `all`.","example":"user"},{"name":"sort","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Single field to sort users by, prefixed with `-` for descending. Either `last_message_time`, `created_date`, `updated_date`, `agent_count`, `conversation_count`, or `credit_count`. For example, `-conversation_count` puts the most active users first. Defaults to `-last_message_time`, and any other field sorts by `last_message_time`.","title":"Sort"},"description":"Single field to sort users by, prefixed with `-` for descending. Either `last_message_time`, `created_date`, `updated_date`, `agent_count`, `conversation_count`, or `credit_count`. For example, `-conversation_count` puts the most active users first. Defaults to `-last_message_time`, and any other field sorts by `last_message_time`.","example":"-last_message_time"},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Items per page. Max 100. Defaults to 20, and `0` also means 20.","title":"Limit"},"description":"Items per page. Max 100. Defaults to 20, and `0` also means 20.","example":20},{"name":"skip","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Number of users to skip before the page starts. Defaults to 0.","title":"Skip"},"description":"Number of users to skip before the page starts. Defaults to 0.","example":20}],"responses":{"200":{"description":"A page of the users who have conversations with the app's agents.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListConversationUsersResponse"}}}},"400":{"description":"A filter has a value it doesn't accept, `agent_filter` names an agent the app doesn't have, `limit` or `skip` is negative or `skip` is too large, `sort` lists more than one field, or `search` or `role_filter` matches more than 5,000 app users."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key."},"404":{"description":"App not found."},"422":{"description":"`limit` or `skip` isn't a whole number."},"429":{"description":"Too many requests for this app in the last minute from personal API keys in your workspace."}}}},"/api/apps/{app_id}/agent-configs/conversations/{conversation_id}":{"get":{"summary":"Get agent conversation","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 conversation with one of the app's agents, with its most recent messages.\n\nThe messages are what the app's user and the agent wrote, so they can hold personal data. They also include what the agent's tools returned.\n\nThis is limited to 120 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key, including a read-only one, from anyone with access to the app, viewers included. Workspace API keys are not authorized for it.</Note>","operationId":"get_conversation_api_apps__app_id__agent_configs_conversations__conversation_id__get","parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the conversation. Get this from [List agent conversation users](/api-reference/list-agent-conversation-users).","title":"Conversation Id"},"description":"ID of the conversation. Get this from [List agent conversation users](/api-reference/list-agent-conversation-users).","example":"68a1d2c3e4f5061728394a5b"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose agents to read.","title":"App Id"},"description":"ID of the app whose agents to read.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The conversation and its most recent messages.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationDetailResponse"}}}},"400":{"description":"`conversation_id` isn't a valid conversation ID."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key."},"404":{"description":"App not found, or the app has no such conversation."},"429":{"description":"Too many requests for this app in the last minute from personal API keys in your workspace."}}}},"/api/apps/{app_id}/agent-configs/usage":{"get":{"summary":"Get agent usage","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 integration credits each of the app's agents used in the workspace's current billing period.\n\nThis is limited to 60 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key, including a read-only one, from anyone with access to the app, viewers included. Workspace API keys are not authorized for it.</Note>","operationId":"get_agent_usage_api_apps__app_id__agent_configs_usage_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose agents to read.","title":"App Id"},"description":"ID of the app whose agents to read.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Integration credits used by each agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentUsageResponse"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key."},"404":{"description":"App not found."},"429":{"description":"Too many requests for this app in the last minute from personal API keys in your workspace."}}}},"/api/apps/{app_id}/agent-configs/{name}/memory":{"get":{"summary":"List agent memories","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 one of the app's agents remembers, newest first.\n\nAn agent saves a memory when it learns something worth keeping. A memory can apply to everyone or to one app user, so it can hold personal data about that user.\n\nYou get up to 200 memories, and there's no pagination.\n\nThis is limited to 60 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance. Some workspaces have a different limit.\n\n<Note>This endpoint accepts a personal API key, including a read-only one, from anyone with access to the app, viewers included. Workspace API keys are not authorized for it.</Note>","operationId":"list_memory_items_api_apps__app_id__agent_configs__name__memory_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","description":"Name of the agent.","title":"Name"},"description":"Name of the agent.","example":"support_agent"},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the app whose agents to read.","title":"App Id"},"description":"ID of the app whose agents to read.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The agent's memories, newest first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AgentMemoryItem"},"title":"Response 200 List Memory Items Api Apps  App Id  Agent Configs  Name  Memory Get"}}}},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this app, or you used a workspace API key."},"404":{"description":"App not found, or the app has no agent with this name."},"429":{"description":"Too many requests for this app in the last minute from personal API keys in your workspace."}}}},"/api/agents":{"post":{"summary":"Create Superagent","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 Superagent that you own. Send it messages through [Create Superagent conversation](/api-reference/create-superagent-conversation), using the `id` this returns as `agent_id`.\n\nThe agent is created in your access token's workspace, or in your default workspace when you use a personal API key. The request waits while the agent's workspace is set up, which takes a few seconds. If setup fails, the agent is removed and you can retry. A retry after a lost response creates a second agent. A personal access token limited to selected apps can't call the new agent afterwards, because the agent isn't on its list.\n\nThis endpoint is limited to 5 requests per minute, shared with [Create app](/api-reference/create-app).\n\n<Note>This endpoint accepts a personal API key or personal access token belonging to a workspace editor. A read-only key is refused, and workspace API keys are not accepted.</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_personal_agent_api_agents_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSuperagent"}}},"required":true},"responses":{"201":{"description":"The new agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Superagent"}}}},"400":{"description":"You have no workspace to create the agent in."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You're a viewer or guest in the workspace, you aren't a member of it, Superagents are turned off in it, or your API key is read-only."},"422":{"description":"`name` is missing or blank, a field is too long, or the body has an unknown field."},"429":{"description":"Rate limit exceeded (5 requests per minute, shared with Create app)."}}}},"/api/agents/{agent_id}/conversations":{"post":{"summary":"Create Superagent conversation","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nReturns your conversation with a Superagent, creating it the first time you call this.\n\nEach user has one main conversation per agent, so calling this again returns the same conversation. If you own the agent, it's the conversation you chat in inside Base44. `metadata` is stored only when the conversation is created. When you already have a conversation, it's returned unchanged and the `metadata` you send is ignored.\n\nSend messages to it with [Send Superagent message](/api-reference/send-superagent-message).\n\n<Note>This endpoint accepts a personal API key or personal access token belonging to a user with access to the agent. Workspace API keys are not accepted.</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":"create_conversation_api_agents__agent_id__conversations_post","parameters":[{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","title":"Agent Id"},"description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConversationPayload"}}}},"responses":{"200":{"description":"Your conversation with the agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuperagentConversationSummary"}}}},"400":{"description":"`agent_id` belongs to an app that isn't a Superagent."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this agent, or your API key is read-only."},"404":{"description":"Agent not found."},"429":{"description":"Rate limit exceeded (100 requests per minute)."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Superagent conversations","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nLists your conversations with a Superagent.\n\nThe list holds at most one conversation, your main conversation with the agent. If you own the agent, it's the conversation you chat in inside Base44. The list is empty until that conversation exists, which [Create Superagent conversation](/api-reference/create-superagent-conversation) makes sure of. Read its messages with [Get Superagent conversation](/api-reference/get-superagent-conversation).\n\n<Note>This endpoint accepts a personal API key or personal access token belonging to a user with access to the agent. Workspace API keys are not accepted.</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_conversations_api_agents__agent_id__conversations_get","parameters":[{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","title":"Agent Id"},"description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"Your conversations with the agent.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SuperagentConversationSummary"},"title":"SuperagentConversations"}}}},"400":{"description":"`agent_id` belongs to an app that isn't a Superagent."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this agent, or your API key is read-only."},"404":{"description":"Agent not found."},"429":{"description":"Rate limit exceeded (100 requests per minute)."}}}},"/api/agents/{agent_id}/conversations/{conversation_id}":{"get":{"summary":"Get Superagent conversation","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 of your conversations with a Superagent, with its latest messages.\n\n`messages` holds the 200 most recent messages, oldest first.\n\n<Note>This endpoint accepts a personal API key or personal access token belonging to a user with access to the agent, including a read-only one. Workspace API keys are not accepted.</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_conversation_api_agents__agent_id__conversations__conversation_id__get","parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the conversation to read. Get it from [Create Superagent conversation](/api-reference/create-superagent-conversation) or [List Superagent conversations](/api-reference/list-superagent-conversations).","title":"Conversation Id"},"description":"ID of the conversation to read. Get it from [Create Superagent conversation](/api-reference/create-superagent-conversation) or [List Superagent conversations](/api-reference/list-superagent-conversations).","example":"68a1c2e4f0b9d3002e7a5c11"},{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","title":"Agent Id"},"description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The conversation, with its latest messages.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuperagentConversation"}}}},"400":{"description":"`agent_id` belongs to an app that isn't a Superagent."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this agent, or the conversation belongs to another user or another agent."},"404":{"description":"Agent or conversation not found."},"429":{"description":"Rate limit exceeded (100 requests per minute)."}}}},"/api/agents/{agent_id}/conversations/{conversation_id}/messages":{"post":{"summary":"Send Superagent 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 a Superagent and waits for its reply.\n\nThe request stays open while the agent works, which includes any tools it runs, and returns the agent's final reply. A reply that needs many tool calls can take minutes, so set a generous client timeout. The agent's turn uses credits from the agent's workspace.\n\nSend `content`, and optionally `file_urls`, in the body. Use a `conversation_id` from [Create Superagent conversation](/api-reference/create-superagent-conversation) or [List Superagent conversations](/api-reference/list-superagent-conversations).\n\nEach request starts its own reply. Sending another message before the agent answers starts a second reply alongside the first, and retrying a request that timed out runs the turn again and uses credits again. Wait for the reply before you send the next message.\n\nTo hear about messages without waiting on this request, subscribe to `message.created` and `message.completed` with [Create Superagent webhook](/api-reference/create-superagent-webhook).\n\nThis endpoint is limited to 20 requests per minute.\n\n<Note>This endpoint accepts a personal API key or personal access token belonging to a user with access to the agent. Workspace API keys are not accepted.</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":"send_message_api_agents__agent_id__conversations__conversation_id__messages_post","parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the conversation to send the message to. Get it from [Create Superagent conversation](/api-reference/create-superagent-conversation) or [List Superagent conversations](/api-reference/list-superagent-conversations).","title":"Conversation Id"},"description":"ID of the conversation to send the message to. Get it from [Create Superagent conversation](/api-reference/create-superagent-conversation) or [List Superagent conversations](/api-reference/list-superagent-conversations).","example":"68a1c2e4f0b9d3002e7a5c11"},{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","title":"Agent Id"},"description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","title":"SendSuperagentMessage","required":["content"],"properties":{"content":{"type":"string","description":"Text of your message to the agent.","example":"Summarize today's sales."},"file_urls":{"type":"array","items":{"type":"string"},"description":"URLs of files to attach to the message, such as a spreadsheet for the agent to read.","example":["https://storage.base44.com/6820f3a4e7b91d003c45a1f2/sales.csv"]}}},"example":{"content":"Summarize today's sales."}}}},"responses":{"200":{"description":"The agent's reply.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuperagentMessage"}}}},"400":{"description":"`agent_id` belongs to an app that isn't a Superagent, or the agent's workspace is out of credits."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this agent, the conversation belongs to another user or another agent, your API key is read-only, or Superagent is turned off for the workspace."},"404":{"description":"Agent or conversation not found."},"429":{"description":"Rate limit exceeded (20 requests per minute), or another message to the agent is being received at the same moment."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/agents/{agent_id}/conversations/{conversation_id}/messages/{message_id}":{"delete":{"summary":"Delete Superagent message","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 message from one of your conversations with a Superagent.\n\nThe agent no longer sees the message in the conversation's history. Deleting a message that doesn't exist, or was already deleted, also succeeds.\n\n<Note>This endpoint accepts a personal API key or personal access token belonging to a user with access to the agent. Workspace API keys are not accepted.</Note>","operationId":"delete_message_api_agents__agent_id__conversations__conversation_id__messages__message_id__delete","parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the conversation the message is in. Get it from [Create Superagent conversation](/api-reference/create-superagent-conversation) or [List Superagent conversations](/api-reference/list-superagent-conversations).","title":"Conversation Id"},"description":"ID of the conversation the message is in. Get it from [Create Superagent conversation](/api-reference/create-superagent-conversation) or [List Superagent conversations](/api-reference/list-superagent-conversations).","example":"68a1c2e4f0b9d3002e7a5c11"},{"name":"message_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the message to delete. Get it from `messages` in [Get Superagent conversation](/api-reference/get-superagent-conversation).","title":"Message Id"},"description":"ID of the message to delete. Get it from `messages` in [Get Superagent conversation](/api-reference/get-superagent-conversation).","example":"3f2504e0-4f89-41d3-9a0c-0305e82c3301"},{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","title":"Agent Id"},"description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The message is deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedSuperagentMessage"}}}},"400":{"description":"`agent_id` belongs to an app that isn't a Superagent."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this agent, your API key is read-only, or the conversation belongs to another user or another agent."},"404":{"description":"Agent or conversation not found."},"429":{"description":"Rate limit exceeded (100 requests per minute)."}}}},"/api/agents/{agent_id}/memory":{"get":{"summary":"List Superagent memories","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nLists what a Superagent remembers that applies to you.\n\nThe list holds the agent's `global` memories, which apply to every user, and your own `user` memories. It returns the 200 most recent, newest first.\n\n<Note>This endpoint accepts a personal API key or personal access token belonging to a user with access to the agent. Workspace API keys are not accepted.</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_memory_items_api_agents__agent_id__memory_get","parameters":[{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","title":"Agent Id"},"description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The memories that apply to you.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SuperagentMemory"},"title":"SuperagentMemories"}}}},"400":{"description":"`agent_id` belongs to an app that isn't a Superagent."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this agent, or your API key is read-only."},"404":{"description":"Agent not found."},"429":{"description":"Rate limit exceeded (100 requests per minute)."}}}},"/api/agents/{agent_id}/memory/{memory_id}":{"delete":{"summary":"Delete Superagent memory","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 of your memories from a Superagent.\n\nYou can delete only your own `user` memories. A `global` memory, which applies to every user of the agent, can't be deleted here.\n\n<Note>This endpoint accepts a personal API key or personal access token belonging to a user with access to the agent. Workspace API keys are not accepted.</Note>","operationId":"delete_memory_item_api_agents__agent_id__memory__memory_id__delete","parameters":[{"name":"memory_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the memory to delete. Get it from [List Superagent memories](/api-reference/list-superagent-memories).","title":"Memory Id"},"description":"ID of the memory to delete. Get it from [List Superagent memories](/api-reference/list-superagent-memories).","example":"68a1c9b2f0b9d3002e7a5c3d"},{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","title":"Agent Id"},"description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"204":{"description":"The memory is deleted."},"400":{"description":"`agent_id` belongs to an app that isn't a Superagent."},"401":{"description":"Missing or invalid credentials."},"403":{"description":"You don't have access to this agent, your API key is read-only, or the memory is `global` or belongs to another user."},"404":{"description":"Agent or memory not found."},"429":{"description":"Rate limit exceeded (100 requests per minute)."}}}},"/api/agents/{agent_id}/webhooks":{"post":{"summary":"Create Superagent webhook","description":"<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>\n\nSubscribes a URL to a Superagent's conversation events.\n\n`events` takes one or more of `message.created`, which fires when a message is added to a conversation, and `message.completed`, which fires when the agent finishes a reply. `target_url` has to be a public HTTPS URL. An agent can have up to 5 webhooks, and each call adds one, so retrying a create that may have succeeded can add a duplicate. Check [List Superagent webhooks](/api-reference/list-superagent-webhooks) first.\n\nBase44 sends each event once and doesn't retry a failed delivery. A failure is recorded in `last_error` and counts toward `consecutive_failures`.\n\nSet `generate_secret` to `true` to sign deliveries. The response then carries `secret`, the only time Base44 shows it.\n\n<Note>This endpoint accepts a personal API key or personal access token belonging to an editor of the agent. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"create_webhook_api_agents__agent_id__webhooks_post","parameters":[{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","title":"Agent Id"},"description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookPayload"}}}},"responses":{"200":{"description":"The new webhook.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuperagentWebhookWithSecret"}}}},"400":{"description":"`agent_id` belongs to an app that isn't a Superagent, or the agent already has 5 webhooks."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"Agent webhooks need the Builder plan or higher."},"403":{"description":"You aren't an editor of this agent, you're a viewer in its workspace, your API key is read-only, or you used a workspace API key."},"404":{"description":"Agent not found."},"429":{"description":"Rate limit exceeded (100 requests per minute)."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Superagent webhooks","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 Superagent's webhooks, newest first.\n\nEach webhook shows whether deliveries are signed (`has_secret`), never the secret itself.\n\n<Note>This endpoint accepts a personal API key or personal access token belonging to an editor of the agent. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"list_webhooks_api_agents__agent_id__webhooks_get","parameters":[{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","title":"Agent Id"},"description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"The agent's webhooks.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SuperagentWebhook"},"title":"SuperagentWebhooks"}}}},"400":{"description":"`agent_id` belongs to an app that isn't a Superagent."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"Agent webhooks need the Builder plan or higher."},"403":{"description":"You aren't an editor of this agent, you're a viewer in its workspace, your API key is read-only, or you used a workspace API key."},"404":{"description":"Agent not found."},"429":{"description":"Rate limit exceeded (100 requests per minute)."}}}},"/api/agents/{agent_id}/webhooks/{webhook_id}":{"patch":{"summary":"Update Superagent webhook","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 Superagent's webhook.\n\nSend only the fields you want to change. Send `description: null` to remove the label. `events` takes one or more of `message.created`, which fires when a message is added to a conversation, and `message.completed`, which fires when the agent finishes a reply. `target_url` has to be a public HTTPS URL.\n\n`enabled: true` turns a webhook back on after Base44 turned it off for failing, and resets `consecutive_failures`. `enabled: false` turns it off. `signing: \"rotate\"` generates a new signing secret and returns it once in `secret`, and `signing: \"disable\"` stops signing deliveries.\n\n<Note>This endpoint accepts a personal API key or personal access token belonging to an editor of the agent. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"update_webhook_api_agents__agent_id__webhooks__webhook_id__patch","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the webhook to change. Get it from [List Superagent webhooks](/api-reference/list-superagent-webhooks).","title":"Webhook Id"},"description":"ID of the webhook to change. Get it from [List Superagent webhooks](/api-reference/list-superagent-webhooks).","example":"68a1d0f4f0b9d3002e7a5c52"},{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","title":"Agent Id"},"description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","example":"6820f3a4e7b91d003c45a1f2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookPayload"}}}},"responses":{"200":{"description":"The updated webhook.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuperagentWebhookWithSecret"}}}},"400":{"description":"`agent_id` belongs to an app that isn't a Superagent."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"Agent webhooks need the Builder plan or higher."},"403":{"description":"You aren't an editor of this agent, you're a viewer in its workspace, your API key is read-only, or you used a workspace API key."},"404":{"description":"Agent or webhook not found."},"429":{"description":"Rate limit exceeded (100 requests per minute)."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete Superagent webhook","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 Superagent's webhook. Base44 stops sending it events right away.\n\n<Note>This endpoint accepts a personal API key or personal access token belonging to an editor of the agent. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"delete_webhook_api_agents__agent_id__webhooks__webhook_id__delete","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the webhook to delete. Get it from [List Superagent webhooks](/api-reference/list-superagent-webhooks).","title":"Webhook Id"},"description":"ID of the webhook to delete. Get it from [List Superagent webhooks](/api-reference/list-superagent-webhooks).","example":"68a1d0f4f0b9d3002e7a5c52"},{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","title":"Agent Id"},"description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"204":{"description":"The webhook is deleted."},"400":{"description":"`agent_id` belongs to an app that isn't a Superagent."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"Agent webhooks need the Builder plan or higher."},"403":{"description":"You aren't an editor of this agent, you're a viewer in its workspace, your API key is read-only, or you used a workspace API key."},"404":{"description":"Agent or webhook not found."},"429":{"description":"Rate limit exceeded (100 requests per minute)."}}}},"/api/agents/{agent_id}/webhooks/{webhook_id}/test":{"post":{"summary":"Test Superagent webhook","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 sample `message.completed` delivery to a Superagent's webhook, so you can check your endpoint.\n\nThe delivery is signed when the webhook has a secret, and carries a unique `X-Base44-Delivery` header. A successful call means the test ran, not that your endpoint accepted it. Read `ok` for that. A failed test doesn't count toward `consecutive_failures`.\n\nBase44 waits up to 10 seconds for your endpoint to answer.\n\n<Note>This endpoint accepts a personal API key or personal access token belonging to an editor of the agent. A read-only key is refused, and workspace API keys are not accepted.</Note>","operationId":"test_webhook_api_agents__agent_id__webhooks__webhook_id__test_post","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the webhook to test. Get it from [List Superagent webhooks](/api-reference/list-superagent-webhooks).","title":"Webhook Id"},"description":"ID of the webhook to test. Get it from [List Superagent webhooks](/api-reference/list-superagent-webhooks).","example":"68a1d0f4f0b9d3002e7a5c52"},{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","title":"Agent Id"},"description":"ID of the Superagent. It's the agent's app ID, shown in the agent's developer settings.","example":"6820f3a4e7b91d003c45a1f2"}],"responses":{"200":{"description":"How your endpoint answered the test.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuperagentWebhookTestResult"}}}},"400":{"description":"`agent_id` belongs to an app that isn't a Superagent."},"401":{"description":"Missing or invalid credentials."},"402":{"description":"Agent webhooks need the Builder plan or higher."},"403":{"description":"You aren't an editor of this agent, you're a viewer in its workspace, your API key is read-only, or you used a workspace API key."},"404":{"description":"Agent or webhook not found."},"429":{"description":"Rate limit exceeded (100 requests per minute)."}}}}},"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},"geo_targets":{"items":{"type":"string"},"type":"array","maxItems":50,"title":"Geo Targets","description":"Locations the campaigns would target, as Google geo target constant IDs. Sharpens the click estimates.","example":["1023191"]}},"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."},"AcceptTransferResponse":{"properties":{"app_id":{"type":"string","title":"App Id","description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"},"app_name":{"type":"string","title":"App Name","description":"Name of the app.","example":"Acme CRM"},"app_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"App Type","description":"Type of the app, for example `user_app` or `user_agent`, or `null` when it isn't set.","example":"user_app"},"new_owner_id":{"type":"string","title":"New Owner Id","description":"ID of the Base44 user who owns the app now.","example":"6706af53b9c1e2004a37d85f"},"target_workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Workspace Id","description":"ID of the workspace the app is in.","example":"67f2c8e01a3b5d004e92d7a1"}},"type":"object","required":["app_id","app_name","new_owner_id"],"title":"AcceptTransferResponse","description":"The app after its ownership changed."},"AccessRequestSummary":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the access request.","example":"68d4a1f7c2b9e5001a7f3c60"},"app_id":{"type":"string","title":"App Id","description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"},"email":{"type":"string","title":"Email","description":"Email of the person.","example":"jane@acme.com"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name","description":"Name of the person, or `null` when they didn't give one.","example":"Jane Cooper"},"role":{"type":"string","title":"Role","description":"App role the person gets when they join.","example":"user"},"status":{"type":"string","title":"Status","description":"`pending` when the person asked for access and is waiting for your review, `approved` when they're invited or approved but haven't joined yet, or `completed` once they've joined.","example":"pending"},"requested_at":{"type":"string","title":"Requested At","description":"When the request or invitation was made, as a UTC timestamp in ISO 8601 format.","example":"2026-09-20T08:12:44.201000Z"},"data":{"additionalProperties":true,"type":"object","title":"Data","description":"Custom `User` fields stored on the invitation, limited by the `User` entity's field-level read rules.","example":{"department":"sales"}}},"type":"object","required":["id","app_id","email","full_name","role","status","requested_at","data"],"title":"AccessRequestSummary","description":"Doc-only: the handler returns plain dicts with exactly these keys."},"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."},"AddFolderAppsRequest":{"properties":{"app_ids":{"items":{"type":"string"},"type":"array","maxItems":500,"title":"App Ids","description":"IDs of the apps to put in the folder, up to 500.","example":["6820f3a4e7b91d003c45a1f2"]}},"type":"object","required":["app_ids"],"title":"AddFolderAppsRequest"},"AddFolderAppsResult":{"properties":{"linked_count":{"type":"integer","title":"Linked Count","description":"Number of apps added to the folder.","example":1},"skipped_count":{"type":"integer","title":"Skipped Count","description":"Number of IDs skipped because the app was already in the folder or was listed twice.","example":0},"folder_id":{"type":"string","title":"Folder Id","description":"ID of the folder.","example":"68c1f2a9b4e7d3005a2c9e11"}},"type":"object","required":["linked_count","skipped_count","folder_id"],"title":"AddFolderAppsResult"},"AgentMemoryItem":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the memory.","example":"68a1d2c3e4f5061728394a7d"},"app_id":{"type":"string","title":"App Id","description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"},"agent_name":{"type":"string","title":"Agent Name","description":"Name of the agent the memory belongs to.","example":"support_agent"},"content":{"type":"string","title":"Content","description":"What the agent remembered, in its own words.","example":"Prefers delivery updates by email rather than SMS."},"scope":{"type":"string","title":"Scope","description":"`global` for a memory the agent uses with every user, or `user` for one it uses only with the user in `user_id`.","example":"user"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id","description":"ID of the app user a `user` memory belongs to, or `null` for a `global` memory.","example":"68a1d2c3e4f5061728394a6c"},"conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversation Id","description":"ID of the conversation the agent saved the memory in, or `null` if it wasn't saved in one.","example":"68a1d2c3e4f5061728394a5b"},"created_date":{"type":"string","title":"Created Date","description":"When the memory was saved, as a UTC timestamp in ISO 8601 format.","example":"2026-01-15T09:31:07"},"updated_date":{"type":"string","title":"Updated Date","description":"When the memory was last changed, as a UTC timestamp in ISO 8601 format.","example":"2026-01-15T09:31:07"}},"type":"object","required":["id","app_id","agent_name","content","scope","created_date","updated_date"],"title":"AgentMemoryItem","description":"One thing an agent remembered."},"AgentSetupSuggestion":{"properties":{"title":{"type":"string","title":"Title","description":"Short name for the suggested agent, up to 40 characters.","example":"Order Tracker"},"description":{"type":"string","title":"Description","description":"One sentence on what the agent does for the app's users, up to 110 characters.","example":"Tells customers where their order is and when it should arrive."},"prompt":{"type":"string","title":"Prompt","description":"Message you can send to the app's AI chat to have it build the agent, up to 300 characters.","example":"Create an agent that answers customers' questions about their Order status and delivery date."}},"type":"object","required":["title","description","prompt"],"title":"AgentSetupSuggestion","description":"One suggested agent."},"AgentSetupSuggestionsResponse":{"properties":{"suggestions":{"items":{"$ref":"#/components/schemas/AgentSetupSuggestion"},"type":"array","title":"Suggestions","description":"Up to 3 suggested agents. The list is empty if none could be generated."}},"type":"object","required":["suggestions"],"title":"AgentSetupSuggestionsResponse","description":"Suggested agents for the app."},"AgentUsageResponse":{"properties":{"usage_by_agent":{"additionalProperties":{"type":"number"},"type":"object","title":"Usage By Agent","description":"Integration credits each agent used in the workspace's current billing period, keyed by agent name. An agent that hasn't used any is left out.","example":{"support_agent":12.5}}},"type":"object","title":"AgentUsageResponse","description":"Integration credits the app's agents used in the current billing period."},"AggregateSpec":{"properties":{"query":{"additionalProperties":true,"type":"object","title":"Query","description":"Filter selecting the records to aggregate, in the same form as `q` on [List entity records](/api-reference/list-entity-records). Leave it out to aggregate every record you can read.","example":{"status":"paid"}},"group_by":{"items":{"type":"string"},"type":"array","title":"Group By","description":"Up to 4 fields to group by, as a list or a single field name. Each row holds one combination of their values. Leave it out, along with `date_bucket`, for one row over all matching records.","example":["status"]},"date_bucket":{"anyOf":[{"$ref":"#/components/schemas/DateBucket"},{"type":"null"}],"description":"Also groups by the period a date falls in, such as the day or month a record was created."},"count":{"type":"boolean","title":"Count","description":"Whether each row includes `count`, the number of records in the group.","default":true,"example":true},"sum":{"items":{"type":"string"},"type":"array","title":"Sum","description":"Number fields to total, as a list or a single field name. Each adds `sum_<field>` to the rows.","example":["amount"]},"avg":{"items":{"type":"string"},"type":"array","title":"Avg","description":"Number fields to average, as a list or a single field name. Each adds `avg_<field>` to the rows.","example":["amount"]},"min":{"items":{"type":"string"},"type":"array","title":"Min","description":"Fields whose smallest value to return, as a list or a single field name. Each adds `min_<field>` to the rows.","example":["created_date"]},"max":{"items":{"type":"string"},"type":"array","title":"Max","description":"Fields whose largest value to return, as a list or a single field name. Each adds `max_<field>` to the rows.","example":["amount"]},"count_distinct":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Count Distinct","description":"One field whose distinct values to count, leaving out `null`. Adds `count_distinct_<field>` to the rows.","example":"customer_email"},"having":{"additionalProperties":true,"type":"object","title":"Having","description":"Filter on the computed values, applied after grouping. Name `count` or a computed field such as `sum_amount`, with a value to match or with `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in` or `$nin`.","example":{"count":{"$gt":1}}},"sort":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sort","description":"Group field or computed field to sort the rows by, prefixed with `-` for descending. Without it, the order of the rows isn't defined.","example":"-sum_amount"},"limit":{"type":"integer","maximum":1000.0,"minimum":1.0,"title":"Limit","description":"Maximum number of rows to return, from 1 to 1000.","default":1000,"example":100}},"additionalProperties":false,"type":"object","title":"AggregateSpec","description":"The wire body of POST /{entity}/aggregate. Keys are camelCase on the wire (the SDK's vocabulary)."},"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 UTC timestamp. Empty when none was produced.","example":"2026-08-25T09:20:11Z"}},"type":"object","required":["explanation","generated_at"],"title":"AnalyzeRunResponse"},"AppDeleteImpact":{"properties":{"has_dependent_templates":{"type":"boolean","title":"Has Dependent Templates","description":"Whether any template listings were made from the app.","example":true},"catalog_items":{"items":{"$ref":"#/components/schemas/DependentTemplate"},"type":"array","title":"Catalog Items","description":"Template listings made from the app that aren't archived.","example":[{"id":"68b2f0c1a4d9e7001c3b5a22","name":"CRM starter","status":"approved","type":"workspace"}]},"has_connected_domains":{"type":"boolean","title":"Has Connected Domains","description":"Whether any custom domains are connected to the app.","example":true},"connected_domains":{"items":{"$ref":"#/components/schemas/ConnectedDomain"},"type":"array","title":"Connected Domains","description":"Custom domains connected to the app, including ones that are turned off. Deleting the app releases them.","example":[{"disabled":false,"domain":"crm.acme.com","id":"68c0d7e2b5a1f4001d2e9b37"}]},"app_users_count":{"type":"integer","title":"App Users Count","description":"Number of users of the app.","example":42},"active_google_ads_campaigns":{"items":{"type":"string"},"type":"array","title":"Active Google Ads Campaigns","description":"Names of the app's Google Ads campaigns that are still serving. [Delete app](/api-reference/delete-app) refuses while this isn't empty.","example":[]}},"type":"object","required":["has_dependent_templates","catalog_items","has_connected_domains","connected_domains","app_users_count","active_google_ads_campaigns"],"title":"AppDeleteImpact","description":"Doc-only: the handler returns a plain dict. ``google_ads_url`` stays\nunpublished: it's a builder page path, not an API link."},"AppEmbeddingPolicyResult":{"properties":{"mode":{"type":"string","enum":["inherit","block_all","allowlist"],"title":"Mode","description":"What the app's own setting says. `inherit` means the app sets no policy and follows its workspace, `block_all` means no site can frame it, and `allowlist` means only `origins` can.","example":"allowlist"},"origins":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Origins","description":"Sites allowed to frame the app when `mode` is `allowlist`, or `null` for the other modes.","example":["https://partners.acme.com"]}},"type":"object","required":["mode","origins"],"title":"AppEmbeddingPolicyResult","description":"An app's own framing policy."},"AppFolderSummary":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the folder.","example":"68c1f2a9b4e7d3005a2c9e11"},"name":{"type":"string","title":"Name","description":"Name of the folder.","example":"Client projects"},"parent_folder_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Folder Id","description":"ID of the folder this one sits in, or `null` for a top-level folder.","example":"68c1f27eb4e7d3005a2c9e0f"},"scope":{"type":"string","enum":["workspace","personal"],"title":"Scope","description":"`workspace` for a folder every member of the workspace sees, or `personal` for one only you see.","example":"workspace"},"position":{"type":"number","title":"Position","description":"Sort key among folders. Lower values come first.","example":2.0},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color","description":"Color set on the folder, or `null` when none is set.","example":"#3B82F6"},"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon","description":"Icon name set on the folder, or `null` when none is set.","example":"briefcase"},"app_type":{"anyOf":[{"type":"string","enum":["user_app","user_agent"]},{"type":"null"}],"title":"App Type","description":"`user_app` for a folder of builder apps, or `user_agent` for a folder of agents. Builder app folders created before agent folders existed leave it out.","example":"user_app"},"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"Time the folder was created, in UTC, as an ISO 8601 timestamp without a time zone offset.","example":"2026-08-01T09:15:00"}},"type":"object","required":["id","name","parent_folder_id","scope","position","created_date"],"title":"AppFolderSummary"},"AppFolderTree":{"properties":{"workspace_folders":{"items":{"$ref":"#/components/schemas/AppFolderSummary"},"type":"array","title":"Workspace Folders","description":"Workspace folders, sorted by `position`.","example":[{"app_type":"user_app","color":"#3B82F6","created_date":"2026-08-01T09:15:00","icon":"briefcase","id":"68c1f2a9b4e7d3005a2c9e11","name":"Client projects","position":2.0,"scope":"workspace"}]},"personal_folders":{"items":{"$ref":"#/components/schemas/AppFolderSummary"},"type":"array","title":"Personal Folders","description":"Your personal folders, sorted by `position`.","example":[{"app_type":"user_app","color":"#3B82F6","created_date":"2026-08-01T09:15:00","icon":"briefcase","id":"68c1f27eb4e7d3005a2c9e0f","name":"Drafts","position":1.0,"scope":"personal"}]},"favorite_folder_ids":{"items":{"type":"string"},"type":"array","title":"Favorite Folder Ids","description":"IDs of the folders in this response that you starred in the Base44 dashboard.","example":["68c1f2a9b4e7d3005a2c9e11"]}},"type":"object","required":["workspace_folders","personal_folders","favorite_folder_ids"],"title":"AppFolderTree"},"AppLogo":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"},"logo_url":{"type":"string","title":"Logo Url","description":"URL of the app's logo, as you sent it.","example":"https://storage.base44.com/6820f3a4e7b91d003c45a1f2/3f2504e0_logo.png"}},"type":"object","required":["id","logo_url"],"title":"AppLogo","description":"Doc-only: the handler returns the whole app document."},"AppMcpConfigPatch":{"properties":{"auth":{"anyOf":[{"type":"string","enum":["none","oauth"]},{"type":"null"}],"title":"Auth","description":"How clients authenticate. Either `\"oauth\"`, where clients sign in, or `\"none\"`, where anyone can call the entity read tools without signing in. Leave it out to keep the current mode.","example":"oauth"},"tools":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Tools","description":"The whole `tools` object, which replaces the stored one. `excluded_agents` lists agents to hold back. `entity_overrides` maps an entity name to the `operations` to serve, where an empty list holds the entity back. `functions` lists the custom tools, and one with `disabled` set to `true` is held back. Leave it out to keep the current tools.","example":{"entity_overrides":{"Invoice":{"operations":[]}},"excluded_agents":["support_bot"],"functions":[]}}},"type":"object","title":"AppMcpConfigPatch","description":"The MCP config keys to change."},"AppMcpConfigResponse":{"properties":{"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config","description":"The editable config, or `null` when the app has no MCP server. It has `auth`, `login_path`, `consent_path`, and the `tools` object that [Update MCP config](/api-reference/update-mcp-config) replaces.","example":{"auth":"oauth","tools":{"entity_overrides":{"Invoice":{"operations":[]}},"excluded_agents":["support_bot"],"functions":[]}}},"tools":{"items":{"$ref":"#/components/schemas/AppMcpToolRow"},"type":"array","title":"Tools","description":"Every tool the config can serve, and whether it does.","default":[]},"pending_removal":{"items":{"$ref":"#/components/schemas/AppMcpLiveToolRow"},"type":"array","title":"Pending Removal","description":"Tools the live server still serves that the config no longer produces. The next publish removes them.","default":[]}},"type":"object","title":"AppMcpConfigResponse","description":"The app's editable MCP config and the tools it resolves to."},"AppMcpLiveToolRow":{"properties":{"name":{"type":"string","title":"Name","description":"Name AI clients call the tool by.","example":"query_invoice"},"kind":{"type":"string","enum":["entity","agent","function"],"title":"Kind","description":"Where the tool comes from. Either `\"entity\"`, `\"agent\"`, or `\"function\"` for a custom tool.","example":"entity"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Display title of the tool, or `null` when it has none.","example":"Query invoices"},"description":{"type":"string","title":"Description","description":"What the tool does, as AI clients see it.","default":"","example":"Find Invoice records by ID or filter."},"read_only_hint":{"type":"boolean","title":"Read Only Hint","description":"Whether the tool is marked as only reading data.","default":false,"example":true}},"type":"object","required":["name","kind"],"title":"AppMcpLiveToolRow","description":"A tool only the live server still has."},"AppMcpStatusResponse":{"properties":{"active":{"type":"boolean","title":"Active","description":"Whether AI clients can use the server right now. It's `false` until the app is published, while it's unpublished or blocked, when the workspace has turned MCP off or requires `oauth` from a `none` server, and when a `none` server's app requires visitors to log in.","default":false,"example":true},"published":{"type":"boolean","title":"Published","description":"Whether a publish has put an MCP server live for the app.","default":false,"example":true},"endpoint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Endpoint","description":"URL AI clients connect to, or `null` when the app has no address yet.","example":"https://my-crm-3c45a1f2.base44.app/api/mcp"},"auth":{"anyOf":[{"type":"string","enum":["none","oauth"]},{"type":"null"}],"title":"Auth","description":"How clients authenticate with the live server, or `null` when none is published. Either `\"oauth\"`, where clients sign in, or `\"none\"`, where anyone can call the public tools.","example":"oauth"},"tools":{"items":{"$ref":"#/components/schemas/AppMcpToolSummary"},"type":"array","title":"Tools","description":"Tools the live server serves. A `none` server lists only its public tools."},"effective_public_settings":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Effective Public Settings","description":"Who can open the app after its next publish, once workspace SSO rules apply. A `none` server only works while this is `public_without_login`.","example":"public_with_login"},"workspace_policy":{"type":"string","title":"Workspace Policy","description":"The workspace's rule for app MCP servers. Either `\"allow\"`, `\"oauth_only\"`, which stops `none` servers, or `\"disabled\"`, which stops every server in the workspace.","default":"allow","example":"allow"}},"type":"object","title":"AppMcpStatusResponse","description":"The app's live MCP server."},"AppMcpToolRow":{"properties":{"key":{"type":"string","title":"Key","description":"Stable ID of the row. Two rows can share a `name`, but never a `key`.","example":"entity:Task:read:"},"name":{"type":"string","title":"Name","description":"Name AI clients call the tool by.","example":"query_task"},"kind":{"type":"string","enum":["entity","agent","function"],"title":"Kind","description":"Where the tool comes from. Either `\"entity\"`, `\"agent\"`, or `\"function\"` for a custom tool.","example":"entity"},"source":{"type":"string","title":"Source","description":"Entity, agent, or custom tool in the config that this row belongs to.","example":"Task"},"operation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operation","description":"Entity operation the tool performs, or `null` for agent and custom tools. One of `read`, `create`, `update`, or `delete`.","example":"read"},"handler":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Handler","description":"Backend function a custom tool runs, or the server route path it calls (starting with `/`), or `null` for other tools.","example":"sendInvoice"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Display title of the tool, or `null` when it has none.","example":"Query tasks"},"description":{"type":"string","title":"Description","description":"What the tool does, as AI clients see it.","default":"","example":"Find Task records by ID or filter."},"read_only_hint":{"type":"boolean","title":"Read Only Hint","description":"Whether the tool is marked as only reading data.","default":false,"example":true},"exposed":{"type":"boolean","title":"Exposed","description":"Whether the config serves the tool.","example":true},"published":{"type":"boolean","title":"Published","description":"Whether the live server already matches `exposed`. It's `false` while a change waits for a publish.","example":true}},"type":"object","required":["key","name","kind","source","exposed","published"],"title":"AppMcpToolRow","description":"A tool the config serves or holds back."},"AppMcpToolSummary":{"properties":{"name":{"type":"string","title":"Name","description":"Name AI clients call the tool by.","example":"query_task"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Display title of the tool, or `null` when it has none.","example":"Query tasks"},"description":{"type":"string","title":"Description","description":"What the tool does, as AI clients see it.","example":"Find Task records by ID or filter."},"kind":{"type":"string","title":"Kind","description":"Where the tool comes from. Either `\"entity\"`, `\"agent\"`, or `\"function\"` for a custom tool backed by a backend function.","example":"entity"},"handler":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Handler","description":"Backend function a custom tool runs, or the server route path it calls (starting with `/`), or `null` for entity and agent tools.","example":"sendInvoice"},"read_only_hint":{"type":"boolean","title":"Read Only Hint","description":"Whether the tool is marked as only reading data.","default":false,"example":true},"public":{"type":"boolean","title":"Public","description":"Whether the tool can be called without signing in when the server's `auth` is `none`. Only entity read tools can. On an `oauth` server every tool still needs a signed-in client.","default":false,"example":false}},"type":"object","required":["name","description","kind"],"title":"AppMcpToolSummary","description":"A tool the live MCP server serves."},"AppOpenApiSpec":{"properties":{"openapi":{"type":"string","title":"Openapi","description":"OpenAPI version of the document.","example":"3.0.3"},"info":{"additionalProperties":true,"type":"object","title":"Info","description":"Title and description of the app's API. The title is the app's name followed by `API`.","example":{"title":"Nordwind Furniture API","version":"1.0.0"}},"servers":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Servers","description":"The app's API address, built from its slug.","example":[{"description":"API server","url":"https://my-crm-3c45a1f2.base44.app/api"}]},"paths":{"additionalProperties":true,"type":"object","title":"Paths","description":"One entry per endpoint of the app's entities, backend functions, and agents.","example":{"/entities/Task":{"get":{"summary":"List Task records"}}}},"components":{"additionalProperties":true,"type":"object","title":"Components","description":"A schema for each entity, and the `api_key` header the app's API authenticates with.","example":{"schemas":{"Task":{"type":"object"}}}}},"type":"object","required":["openapi","info","servers","paths","components"],"title":"AppOpenApiSpec","description":"An OpenAPI 3.0.3 document describing the app's own API."},"AppRedirectURIsResponse":{"properties":{"builder_redirect_uri":{"type":"string","title":"Builder Redirect Uri","description":"Redirect URI for connections made while building the app in Base44.","example":"https://app.base44.com/api/external-auth/callback"},"app_redirect_uris":{"items":{"$ref":"#/components/schemas/RedirectURI"},"type":"array","title":"App Redirect Uris","description":"Redirect URIs for connections the app's users make in the published app, one for each host it serves on: Base44 hosts first, then custom domains, each sorted by host. Empty for an app that has no name yet.","example":[{"host":"acme-crm.base44.app","is_custom_domain":false,"uri":"https://acme-crm.base44.app/api/external-auth/callback"},{"host":"crm.acme.com","is_custom_domain":true,"uri":"https://crm.acme.com/api/external-auth/callback"}]},"gateway_redirect_uri":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Redirect Uri","description":"Single redirect URI that covers every host, for workspace connectors that use the gateway callback. Left out when the gateway callback isn't available to you.","example":"https://connectors.base44.com/api/oauth/callback"}},"type":"object","required":["builder_redirect_uri","app_redirect_uris"],"title":"AppRedirectURIsResponse","description":"The redirect URIs to register in a workspace connector's own OAuth app for this app."},"AppSSOSettings":{"properties":{"name":{"type":"string","title":"Name","description":"Name of the app's SSO provider, or an empty string when none is set. The builder uses `google`, `microsoft`, `github`, or `okta` for those providers. Any other name is a custom OpenID Connect or OAuth provider.","example":"okta"},"client_id":{"type":"string","title":"Client Id","description":"OAuth client ID from the identity provider, or an empty string when none is stored.","example":"0oa8f2k1xyzAbCdE5d7"},"client_secret":{"type":"string","title":"Client Secret","description":"Reads `XXXXXXXXXXXXXXXXXXXX` when a client secret is stored, and an empty string when none is. The secret itself is never returned.","example":"XXXXXXXXXXXXXXXXXXXX"},"discovery_url":{"type":"string","title":"Discovery Url","description":"OpenID Connect discovery URL, or an empty string when none is stored or the provider doesn't use one.","example":"https://acme.okta.com/.well-known/openid-configuration"},"scope":{"type":"string","title":"Scope","description":"Scopes requested at sign-in, separated by spaces. Reads `openid email profile` when none is stored or the provider doesn't use one.","example":"openid email profile"},"auth_endpoint":{"type":"string","title":"Auth Endpoint","description":"Authorization endpoint for a provider without a discovery URL, or an empty string when none is stored or the provider doesn't use one.","example":""},"token_endpoint":{"type":"string","title":"Token Endpoint","description":"Token endpoint for a provider without a discovery URL, or an empty string when none is stored or the provider doesn't use one.","example":""},"userinfo_endpoint":{"type":"string","title":"Userinfo Endpoint","description":"User info endpoint for a provider without a discovery URL, or an empty string when none is stored or the provider doesn't use one.","example":""},"jwks_uri":{"type":"string","title":"Jwks Uri","description":"URL of the provider's signing keys, for a custom provider. The value is an empty string when none is stored or the provider doesn't use one.","example":""},"tenant_id":{"type":"string","title":"Tenant Id","description":"Microsoft Entra tenant ID, or an empty string when none is stored or the provider isn't `microsoft`.","example":""},"okta_domain":{"type":"string","title":"Okta Domain","description":"Okta domain, or an empty string when none is stored or the provider isn't `okta`.","example":"acme.okta.com"}},"type":"object","required":["name","client_id","client_secret","discovery_url","scope","auth_endpoint","token_endpoint","userinfo_endpoint","jwks_uri","tenant_id","okta_domain"],"title":"AppSSOSettings"},"AppSSOSettingsResponse":{"properties":{"settings":{"$ref":"#/components/schemas/AppSSOSettings","description":"The provider and its non-secret settings."}},"type":"object","required":["settings"],"title":"AppSSOSettingsResponse","description":"The app's own SSO provider settings, with the client secret masked."},"AppSSOUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Name of the SSO provider. Use `google`, `microsoft`, `github`, or `okta` for those providers, or a name of your choice for any other OpenID Connect or OAuth provider. Required unless the app already has a provider.","example":"okta"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id","description":"OAuth client ID from the identity provider.","example":"0oa8f2k1xyzAbCdE5d7"},"client_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Secret","description":"OAuth client secret from the identity provider. Leave it out to keep the stored one.","example":"kq3Vt1-9dPzLr0aYbN2x"},"discovery_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Discovery Url","description":"OpenID Connect discovery URL. It must be an absolute `http` or `https` URL on a public address.","example":"https://acme.okta.com/.well-known/openid-configuration"},"scope":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope","description":"Scopes to request at sign-in, separated by spaces.","example":"openid email profile"},"auth_endpoint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Auth Endpoint","description":"Authorization endpoint, for a provider without a discovery URL.","example":"https://github.com/login/oauth/authorize"},"token_endpoint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token Endpoint","description":"Token endpoint, for a provider without a discovery URL.","example":"https://github.com/login/oauth/access_token"},"userinfo_endpoint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Userinfo Endpoint","description":"User info endpoint, for a provider without a discovery URL.","example":"https://api.github.com/user"},"jwks_uri":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jwks Uri","description":"URL of the provider's signing keys, for a custom provider.","example":"https://idp.acme.com/oauth2/keys"},"tenant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id","description":"Microsoft Entra tenant ID, for the `microsoft` provider.","example":"organizations"},"okta_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Okta Domain","description":"Okta domain, for the `okta` provider.","example":"acme.okta.com"}},"type":"object","title":"AppSSOUpdateRequest","description":"The app's SSO provider and the settings to change, sent together."},"AppSSOUpdateResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Always `success`.","example":"success"},"auth_config":{"additionalProperties":true,"type":"object","title":"Auth Config","description":"The app's sign-in settings as saved, with `sso_provider_name` set to the provider and `enable_sso_login` set to `true`.","example":{"enable_apple_login":false,"enable_facebook_login":false,"enable_google_login":true,"enable_microsoft_login":false,"enable_sso_login":true,"enable_username_password":false,"sso_provider_name":"okta"}},"warning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Warning","description":"Why the discovery URL couldn't be checked, present only when that happened. The settings are saved, but SSO sign-in fails while the problem lasts.","example":"The Discovery URL could not be verified because it did not respond in time. SSO login will fail while that persists."}},"type":"object","required":["status","auth_config"],"title":"AppSSOUpdateResponse","description":"The result of saving the app's SSO provider settings."},"AppSocialImage":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"},"social_image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Social Image Url","description":"URL of the image shown when someone shares the app's link, or `null` when you removed it.","example":"https://storage.base44.com/6820f3a4e7b91d003c45a1f2/7c1e9f2a_share.png"}},"type":"object","required":["id","social_image_url"],"title":"AppSocialImage","description":"Doc-only: the handler returns the whole app document."},"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."},"ApplicationEmailPreview":{"properties":{"type":{"type":"string","enum":["password_reset","email_verification","user_invite","access_approved"],"title":"Type","description":"The application email.","example":"password_reset"},"subject":{"type":"string","title":"Subject","description":"The subject line recipients see.","example":"Reset your password for Acme Bookings"},"html":{"type":"string","title":"Html","description":"The email as HTML, with sample values in place of the recipient's details, such as the name `Alex` and a sample code or link.","example":"<!doctype html><html><body>Hi Alex, reset your password for Acme Bookings.</body></html>"},"authored_template_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authored Template Id","description":"ID of the app's own template for this email, or `null` when the app has none. Having one doesn't mean it's sent: for `email_verification`, `authored_status` says which version goes out. Delete it with [Delete email template](/api-reference/delete-email-template) to go back to the Base44 version.","example":"68c1f0a2b3d4e5f6a7b8c9d0"},"authored_status":{"anyOf":[{"type":"string","enum":["live","no_email_domain","awaiting_screen","rejected","missing_code","over_daily_budget","too_large","mjml"]},{"type":"null"}],"title":"Authored Status","description":"Only for `email_verification`, which goes to people who haven't signed up yet, so the app's own version has extra requirements. `live` when the app's own version is sent. Otherwise, the reason the Base44 version is sent instead: `no_email_domain` until the app sends from its own email domain, `awaiting_screen` while the app's version is being reviewed, which can take up to an hour, `rejected` when it didn't pass review, `missing_code` when it doesn't show the verification code, `over_daily_budget` when it reached today's sending limit, `too_large` when it's too large to send, and `mjml` when it's written in MJML rather than HTML. `no_email_domain` is reported even when the app has no version of its own. `null` for the other emails, and for `email_verification` when the app's plan doesn't include its own version, or when the app sends from its own email domain but has no version of its own.","example":"live"}},"type":"object","required":["type","subject","html","authored_template_id","authored_status"],"title":"ApplicationEmailPreview","description":"An application email as the app sends it."},"AppliedIncentiveResult":{"properties":{"applied_incentive_code":{"type":"string","title":"Applied Incentive Code","description":"Google's coupon code for the incentive the account now holds. When the account already held one, this is that incentive's code rather than the offer you sent.","example":"9TQGLXDH6Q74EU"},"account_id":{"type":"string","title":"Account Id","description":"ID of the Google Ads account the incentive applies to.","example":"68b1c0d4e7b91d003c45a1f8"}},"type":"object","required":["applied_incentive_code","account_id"],"title":"AppliedIncentiveResult","description":"The incentive the Google Ads account holds after the call."},"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"},"ArchivedTemplateListing":{"properties":{"message":{"type":"string","title":"Message","description":"Confirmation message.","example":"Item archived successfully"}},"type":"object","required":["message"],"title":"ArchivedTemplateListing","description":"Confirmation that the template listing is archived."},"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."},"AssetSummary":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the asset.","example":"68b0e1f2a3c4d5e6f7a8b9c0"},"app_id":{"type":"string","title":"App Id","description":"ID of the app the asset belongs to.","example":"6820f3a4e7b91d003c45a1f2"},"file_name":{"type":"string","title":"File Name","description":"Name of the file.","example":"Logo-dark.png"},"file_url":{"type":"string","title":"File Url","description":"URL of the file.","example":"https://media.base44.com/files/public/6820f3a4e7b91d003c45a1f2/Logo-dark.png"},"source_type":{"type":"string","enum":["upload","generated"],"title":"Source Type","description":"How the file got into the library. `upload` for a file someone uploaded, and `generated` for most files Base44 created for the app, such as AI-generated images and videos or a Figma import.","example":"upload"},"file_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Type","description":"File extension without the dot, usually lowercase, such as `png` or `mp4`. The value is `null` when the file was added without one.","example":"png"},"created_date":{"type":"string","title":"Created Date","description":"When the asset was added to the library, as a UTC timestamp in ISO 8601 format.","example":"2026-01-15T09:23:41"},"updated_date":{"type":"string","title":"Updated Date","description":"When the asset was last changed, such as renamed, as a UTC timestamp in ISO 8601 format.","example":"2026-01-15T09:31:07"}},"type":"object","required":["id","app_id","file_name","file_url","source_type","file_type","created_date","updated_date"],"title":"AssetSummary","description":"A file in the app's media library."},"Assets":{"items":{"$ref":"#/components/schemas/AssetSummary"},"type":"array","title":"Assets","description":"The app's assets."},"AuthConfiguration":{"properties":{"enable_username_password":{"type":"boolean","title":"Enable Username Password","description":"Whether people can sign in to the app with an email and password.","default":false,"example":true},"enable_google_login":{"type":"boolean","title":"Enable Google Login","description":"Whether people can sign in to the app with a Google account.","default":true,"example":true},"enable_microsoft_login":{"type":"boolean","title":"Enable Microsoft Login","description":"Whether people can sign in to the app with a Microsoft account.","default":false,"example":false},"enable_facebook_login":{"type":"boolean","title":"Enable Facebook Login","description":"Whether people can sign in to the app with a Facebook account.","default":false,"example":false},"enable_apple_login":{"type":"boolean","title":"Enable Apple Login","description":"Whether people can sign in to the app with an Apple account.","default":false,"example":false}},"type":"object","title":"AuthConfiguration","description":"Authentication configuration for user applications"},"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."},"BackendFunction":{"properties":{"name":{"type":"string","title":"Name","description":"Name of the function. Nested functions have slashes in their name.","example":"sendWelcomeEmail"},"entry":{"type":"string","title":"Entry","description":"Path of the file the function starts from, one of the `path` values in `files`.","example":"main.ts"},"files":{"items":{"$ref":"#/components/schemas/BackendFunctionFile"},"type":"array","title":"Files","description":"The function's deployed code.","example":[{"content":"Deno.serve(async (req) => {\n  return Response.json({ ok: true });\n});\n","path":"main.ts"}]},"automations":{"items":{"$ref":"#/components/schemas/BackendFunctionAutomation"},"type":"array","title":"Automations","description":"Automations that run the function. Empty when it has none.","example":[{"description":"Emails each user a summary of yesterday's tasks.","function_args":{"digest":"daily"},"is_active":true,"name":"Nightly digest","type":"scheduled"}]}},"type":"object","required":["name","entry","files","automations"],"title":"BackendFunction","description":"A backend function deployed for the app."},"BackendFunctionAutomation":{"properties":{"name":{"type":"string","title":"Name","description":"Name of the automation.","example":"Nightly digest"},"type":{"type":"string","enum":["scheduled","entity","connector"],"title":"Type","description":"What starts the automation. `scheduled` runs it on a schedule, `entity` when records of an entity change, and `connector` on an event from a connected service. Each automation also carries the settings for its type, such as its schedule or the entity it watches.","example":"scheduled"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"What the automation is for, or `null` if none was given.","example":"Emails each user a summary of yesterday's tasks."},"function_args":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Function Args","description":"Arguments the automation passes to the function, or `null` when it passes none.","example":{"digest":"daily"}},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the automation runs (`true`) or is paused (`false`).","example":true}},"type":"object","required":["name","type","description","function_args","is_active"],"title":"BackendFunctionAutomation","description":"An automation that runs a backend function."},"BackendFunctionFile":{"properties":{"path":{"type":"string","title":"Path","description":"Path of the file in the function's deployment.","example":"main.ts"},"content":{"type":"string","title":"Content","description":"The file's source.","example":"Deno.serve(async (req) => {\n  return Response.json({ ok: true });\n});\n"}},"type":"object","required":["path","content"],"title":"BackendFunctionFile","description":"One file of a backend function's code."},"BackendFunctions":{"properties":{"functions":{"items":{"$ref":"#/components/schemas/BackendFunction"},"type":"array","title":"Functions","description":"The functions, with their code and automations. Empty when the app has none.","example":[{"automations":[],"entry":"main.ts","files":[{"content":"Deno.serve(async (req) => {\n  return Response.json({ ok: true });\n});\n","path":"main.ts"}],"name":"sendWelcomeEmail"}]}},"type":"object","required":["functions"],"title":"BackendFunctions","description":"The app's deployed backend functions."},"BatchRestoreStatusItem":{"properties":{"operation_id":{"type":"string","title":"Operation Id","description":"ID of this entity's part of the restore, as `operation_id` in `launched`.","example":"68a1c2f0e4f5a6b7c8d9e1a2"},"entity_name":{"type":"string","title":"Entity Name","description":"Entity this part of the restore rewrites.","example":"Invoice"},"status":{"type":"string","enum":["pending","running","succeeded","failed"],"title":"Status","description":"Where this entity's restore is, one of `pending`, `running`, `succeeded`, or `failed`. Only `succeeded` and `failed` are final.","example":"succeeded"},"counts":{"$ref":"#/components/schemas/RestoreCounts","description":"How many records the restore has changed in this entity so far."},"pre_restore_checkpoint_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pre Restore Checkpoint Id","description":"ID of the backup Base44 saved of this entity just before rewriting it, or `null` until it's saved. Restore this entity alone from it to undo its part of the restore.","example":"68a1c30ae4f5a6b7c8d9e1b3"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why this entity's restore failed, in plain language, or `null` unless `status` is `failed`.","example":"This restore point is no longer available."}},"type":"object","required":["operation_id","entity_name","status","counts"],"title":"BatchRestoreStatusItem","description":"Where one entity's part of a restore has got to."},"BatchRestoreStatusResponse":{"properties":{"operations":{"items":{"$ref":"#/components/schemas/BatchRestoreStatusItem"},"type":"array","title":"Operations","description":"One entry per entity in the restore, oldest first. Empty when the app has no restore with this ID.","example":[{"counts":{"deleted":3,"reinserted":1,"reverted":12},"entity_name":"Invoice","operation_id":"68a1c2f0e4f5a6b7c8d9e1a2","pre_restore_checkpoint_id":"68a1c30ae4f5a6b7c8d9e1b3","status":"succeeded"}]},"restore_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Restore To","description":"The moment the entities are restored to, as a UTC timestamp in ISO 8601 format with a `Z` suffix, or `null` when `operations` is empty.","example":"2026-06-04T14:07:02.115000Z"},"undo_checkpoint_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Undo Checkpoint Id","description":"ID of the checkpoint that undoes the whole restore, or `null` until Base44 has saved the first backup. Restore the same entities from it to put them all back the way they were before.","example":"68a1c30ae4f5a6b7c8d9e1b3"}},"type":"object","required":["operations"],"title":"BatchRestoreStatusResponse","description":"Where each entity in a restore has got to."},"BatchResult":{"properties":{"files":{"additionalProperties":{"$ref":"#/components/schemas/FileResult"},"type":"object","title":"Files","description":"Each file, keyed by its path relative to the app root.","example":{"src/pages/Home.jsx":{"content":"export default function Home() {\n  return <h1>Home</h1>;\n}\n"},"src/pages/Missing.jsx":{"error":"file_not_found"}}}},"type":"object","required":["files"],"title":"BatchResult","description":"The text of each file you asked for."},"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."},"BranchRunState":{"properties":{"state":{"type":"string","enum":["ready","processing","error"],"title":"State","description":"`processing` while the AI is working on the branch, `ready` once it's idle, and `error` when the last turn on the branch failed.","example":"ready"},"details":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Details","description":"Short note about the state, such as why the last turn failed, or `null` when there's nothing to report.","example":"Restoring checkpoint"},"last_updated_date":{"type":"string","format":"date-time","title":"Last Updated Date","description":"When the state last changed, as a UTC timestamp in ISO 8601 format.","example":"2026-09-28T10:18:40"}},"type":"object","required":["state","details","last_updated_date"],"title":"BranchRunState"},"BranchSummary":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the branch.","example":"68f1a2b3c4d5e6f708192a3b"},"app_id":{"type":"string","title":"App Id","description":"ID of the app the branch belongs to.","example":"6820f3a4e7b91d003c45a1f2"},"branch_name":{"type":"string","title":"Branch Name","description":"Name of the branch.","example":"add-contact-form"},"status":{"type":"string","enum":["active","merged","deleted"],"title":"Status","description":"`active` while the branch can still be worked on, `merged` once it was merged into main, and `deleted` once it was deleted.","example":"active"},"run_state":{"anyOf":[{"$ref":"#/components/schemas/BranchRunState"},{"type":"null"}],"description":"State of the AI's work on the branch, or `null` if no turn has run on it yet."},"code_state":{"type":"string","enum":["unchanged","changed"],"title":"Code State","description":"`unchanged` when the branch has no code changes of its own to merge into main. `changed` otherwise, including when Base44 can't tell yet.","example":"changed"},"base_checkpoint_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Base Checkpoint Id","description":"ID of the main [checkpoint](/api-reference/list-checkpoints) the branch started from, or `null` if the app had no checkpoint yet.","example":"6886b8d390dc7e2f4a2c91b3"},"created_by_id":{"type":"string","title":"Created By Id","description":"ID of the user who created the branch.","example":"68a0c1d2e3f4a5b6c7d8e9f0"},"created_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Name","description":"Display name of the user who created the branch, or the part of their email before the `@` when they have no name. `null` when Base44 can't name them, for example when their account no longer exists.","example":"Jane Doe"},"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"When the branch was created, as a UTC timestamp in ISO 8601 format.","example":"2026-09-28T10:15:00"},"merged_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Merged At","description":"When the branch was merged into main, as a UTC timestamp in ISO 8601 format, or `null` if it wasn't.","example":"2026-09-29T08:02:11"},"merged_by_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Merged By Id","description":"ID of the user who merged the branch, or `null` if it wasn't merged or Base44 doesn't know who did, as for a branch whose pull request was merged on GitHub.","example":"68a0c1d2e3f4a5b6c7d8e9f0"},"static_preview_url":{"type":"string","title":"Static Preview Url","description":"URL of the branch's preview. It shows the branch only while it's active and once it has been built.","example":"https://preview--6820f3a4e7b91d003c45a1f2--b-d8c647b.base44.app"}},"type":"object","required":["id","app_id","branch_name","status","run_state","code_state","base_checkpoint_id","created_by_id","created_by_name","created_date","merged_at","merged_by_id","static_preview_url"],"title":"BranchSummary","description":"A branch of an app."},"BranchSyncResult":{"properties":{"branch":{"$ref":"#/components/schemas/BranchSummary","description":"The branch after the update."},"sync_status":{"$ref":"#/components/schemas/BranchSyncStatus","description":"How the branch stands against main after the update."},"updated":{"type":"boolean","title":"Updated","description":"`false` when the branch was already up to date with main, so nothing changed. `true` when the update was applied, or when it's waiting for your input (`sync_status.sync_state` is `needs_input`).","example":true}},"type":"object","required":["branch","sync_status","updated"],"title":"BranchSyncResult","description":"The branch after the update."},"BranchSyncStatus":{"properties":{"new_main_message_count":{"type":"integer","title":"New Main Message Count","description":"How many chat messages people sent on main since the branch was created or last updated from main.","example":2},"main_code_changed":{"type":"boolean","title":"Main Code Changed","description":"`true` when main's code changed since the branch was created or last updated from main.","example":true},"conflict_state":{"type":"string","enum":["clear","overlap","unavailable"],"title":"Conflict State","description":"Whether updating the branch from main is expected to conflict. `clear` when main's code hasn't changed, so an update can't conflict. `overlap` when the update is expected to conflict, or while conflicts from an update are being resolved. `unavailable` when Base44 can't tell, including while an update runs. Once main's code has changed, it's never `clear`, even when the update would apply cleanly.","example":"unavailable"},"sync_state":{"type":"string","enum":["idle","updating","resolving","needs_input"],"title":"Sync State","description":"`idle` when no update is running. `updating` while main's changes are being applied, `resolving` while the AI resolves conflicts, and `needs_input` when the AI needs an answer to finish resolving them. Answer it in the Base44 editor. Until the state is back to `idle`, the branch can't be merged or deleted.","example":"idle"},"blocked_reason":{"anyOf":[{"type":"string","enum":["app_processing","branch_processing"]},{"type":"null"}],"title":"Blocked Reason","description":"Why [Update branch from main](/api-reference/update-branch-from-main) would be refused right now: `app_processing` while the AI is working on main, and `branch_processing` while it's working on the branch. `null` when neither applies. An update is also refused while `sync_state` is `resolving` or `needs_input`.","example":"app_processing"}},"type":"object","required":["new_main_message_count","main_code_changed","conflict_state","sync_state","blocked_reason"],"title":"BranchSyncStatus","description":"How the branch stands against main."},"Branches":{"items":{"$ref":"#/components/schemas/BranchSummary"},"type":"array","title":"Branches","description":"The app's branches."},"BrandDescriptionRequest":{"properties":{"description":{"type":"string","title":"Description","description":"What the brand is: the business, its audience, and the look and voice you want.","example":"A family furniture workshop that builds solid oak tables. Warm, earthy, and plain-spoken."}},"type":"object","required":["description"],"title":"BrandDescriptionRequest"},"BrandUpsertRequest":{"properties":{"kit":{"additionalProperties":true,"type":"object","title":"Kit","description":"The brand's identity. It needs a non-empty `title`, which becomes the brand's title, and a `description`. Brands Base44 generates also carry `tagLine`, `values`, `toneOfVoice`, `designGuidelines`, `componentStyle`, and a `logo` with its `url`. Nothing else in it is checked.","example":{"description":"A family furniture workshop that builds solid oak tables and chairs to order.","logo":{"altText":"Nordwind logo","url":"https://media.base44.com/images/public/nordwind-logo.png"},"tagLine":"Furniture built to last","title":"Nordwind","toneOfVoice":["Warm","Plain-spoken"],"values":["Craft","Honesty","Durability"]}},"design_system":{"additionalProperties":true,"type":"object","title":"Design System","description":"The brand's design system: its `tokens`, such as `colors` and `fonts`, and its component sections. It's stored as you send it. When you include `tokens`, send it as an object. Defaults to an empty object.","example":{"tokens":{"colors":[{"label":"Primary","token":"primary","value":"#0B7A3B"},{"label":"Background","token":"background","value":"#FAF7F2"}],"fonts":[{"family":"Fraunces","role":"heading"},{"family":"Inter","role":"body"}]}}}},"type":"object","required":["kit"],"title":"BrandUpsertRequest"},"BudgetEstimateRequest":{"properties":{"landing_page":{"type":"string","title":"Landing Page","description":"Landing page the campaign would send clicks to. Required for a forecast.","default":"","example":"https://example.com/furniture"},"geo_targets":{"items":{"type":"string"},"type":"array","maxItems":50,"title":"Geo Targets","description":"Locations to forecast for, as Google geo target constant IDs (`geoTargetConstants/1023191`, or just `1023191`). At least one is required for a forecast.","example":["1023191"]},"keyword_themes":{"items":{"type":"string"},"type":"array","maxItems":25,"title":"Keyword Themes","description":"Themes the campaign would match on. An empty list forecasts nothing useful.","example":["handmade oak furniture","custom dining tables"]},"language_code":{"type":"string","title":"Language Code","description":"Two-letter language the ads would run in. Defaults to `en`.","default":"","example":"en"},"business_name":{"type":"string","title":"Business Name","description":"Business name, which sharpens Google's forecast.","default":"","example":"Oakline Furniture"},"daily_budget":{"type":"integer","title":"Daily Budget","description":"Daily budget you have in mind, in micros (1,000,000 micros = 1 unit of the account's currency). Google's planner chooses the budget it forecasts at, so this is only reported back when Google returns no recommendation of its own.","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."},"BulkInvitationItem":{"properties":{"email":{"type":"string","title":"Email","description":"Email of the person to invite.","example":"dana@acme.com"},"role":{"type":"string","enum":["admin","editor","viewer"],"description":"Role the invitee gets when they accept. Defaults to `viewer`. `admin` needs the Business plan or higher.","default":"viewer","example":"editor"},"group_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Group Id","description":"ID of a custom group to add the invitee to when they accept. Get it from [List workspace groups](/api-reference/list-workspace-groups). Needs a plan that includes groups.","example":"68b4e1d2c9a7f3001e2d4b61"}},"type":"object","required":["email"],"title":"BulkInvitationItem","description":"One invitation in a bulk invite."},"BulkInvitationResult":{"properties":{"total_sent":{"type":"integer","title":"Total Sent","description":"Number of invitations sent, the length of `successful`.","example":1},"successful":{"items":{"type":"string"},"type":"array","title":"Successful","description":"Emails an invitation was sent to.","example":["dana@acme.com"]},"failed":{"items":{"$ref":"#/components/schemas/FailedInvitation"},"type":"array","title":"Failed","description":"Invitations that weren't sent, with the reason. Empty when every invitation went out.","example":[{"email":"sam@acme.com","error":"User already has a pending invitation"}]}},"type":"object","required":["total_sent","successful","failed"],"title":"BulkInvitationResult","description":"Which invitations went out."},"BulkInviteRequest":{"properties":{"invitations":{"items":{"$ref":"#/components/schemas/BulkInvitationItem"},"type":"array","maxItems":50,"minItems":1,"title":"Invitations","description":"The people to invite, 1 to 50. Each email can appear once.","example":[{"email":"dana@acme.com","role":"editor"},{"email":"sam@acme.com"}]}},"type":"object","required":["invitations"],"title":"BulkInviteRequest","description":"Invitations to send in one request."},"BulkMemberCreditLimitItem":{"properties":{"email":{"type":"string","minLength":1,"title":"Email","description":"Email of the member, exactly as `email` in [List workspace members](/api-reference/list-workspace-members) returns it. Matching is exact, including case.","example":"dana@acme.com"},"credit_limit":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Credit Limit","description":"Most credits the member can use in the workspace each month, at least 1. `null`, or leaving it out, removes their own limit, so the workspace default applies.","example":500}},"type":"object","required":["email"],"title":"BulkMemberCreditLimitItem","description":"One member's credit limit."},"BulkMemberOperationResponse":{"properties":{"results":{"items":{"$ref":"#/components/schemas/BulkMemberResult"},"type":"array","title":"Results","description":"One entry per item, in request order.","example":[{"email":"dana@acme.com","message":"Member role updated to editor.","status":"success"}]}},"type":"object","required":["results"],"title":"BulkMemberOperationResponse","description":"The outcome of each item."},"BulkMemberResult":{"properties":{"email":{"type":"string","title":"Email","description":"Email of the member.","example":"dana@acme.com"},"status":{"type":"string","enum":["success","skipped"],"title":"Status","description":"`success` when the change was applied, `skipped` when the member changed while the request ran and nothing was applied.","default":"success","example":"success"},"message":{"type":"string","title":"Message","description":"Short explanation. Its wording can change, so don't parse it.","example":"Member role updated to editor."}},"type":"object","required":["email","status","message"],"title":"BulkMemberResult","description":"What happened to one item."},"BulkMemberRoleItem":{"properties":{"email":{"type":"string","minLength":1,"title":"Email","description":"Email of the member, exactly as `email` in [List workspace members](/api-reference/list-workspace-members) returns it. Matching is exact, including case.","example":"dana@acme.com"},"role":{"type":"string","enum":["admin","editor","viewer"],"description":"The member's new role. `admin` needs the Business plan or higher.","example":"editor"}},"type":"object","required":["email","role"],"title":"BulkMemberRoleItem","description":"One member's role change."},"BulkRemoveMembersRequest":{"properties":{"emails":{"items":{"type":"string"},"type":"array","maxItems":200,"minItems":1,"title":"Emails","description":"Emails of the members or pending invitees to remove, 1 to 200, exactly as `email` in [List workspace members](/api-reference/list-workspace-members) returns them. Each email can appear once.","example":["dana@acme.com","sam@acme.com"]}},"type":"object","required":["emails"],"title":"BulkRemoveMembersRequest","description":"People to remove in one request."},"BulkResendInviteRequest":{"properties":{"emails":{"items":{"type":"string"},"type":"array","maxItems":50,"minItems":1,"title":"Emails","description":"Emails the pending invitations were sent to, 1 to 50. Each email can appear once.","example":["dana@acme.com","sam@acme.com"]}},"type":"object","required":["emails"],"title":"BulkResendInviteRequest","description":"Pending invitations to send again."},"BulkRestoreRequest":{"properties":{"entity_names":{"items":{"type":"string"},"type":"array","title":"Entity Names","description":"Entities to restore, as [List entity schemas](/api-reference/list-entity-schemas) reports them. Name at least one and at most 300. They don't have to include the entity the checkpoint belongs to.","example":["Invoice","Customer"]}},"type":"object","required":["entity_names"],"title":"BulkRestoreRequest","description":"Which entities to restore to the checkpoint."},"BulkRestoreStartResponse":{"properties":{"batch_id":{"type":"string","title":"Batch Id","description":"ID of the restore. Pass it to [Get data restore status](/api-reference/get-data-restore-status) to follow it.","example":"3f1c9a52-7b4e-4d2a-9c61-5e8f0b7a2d14"},"launched":{"items":{"$ref":"#/components/schemas/LaunchedRestore"},"type":"array","title":"Launched","description":"The entities whose restore started, each with the ID of its own part of the restore.","example":[{"entity_name":"Invoice","operation_id":"68a1c2f0e4f5a6b7c8d9e1a2"}]},"skipped_entities":{"items":{"type":"string"},"type":"array","title":"Skipped Entities","description":"Entities you asked for that didn't start, because another restore was already running on them. Try them again once it finishes.","example":[]}},"type":"object","required":["batch_id","launched","skipped_entities"],"title":"BulkRestoreStartResponse","description":"The restore that started, and which entities it covers."},"BulkToggleAction":{"properties":{"integration_type":{"type":"string","title":"Integration Type","description":"ID of the connector, from `integration_type` in [Get workspace connector policies](/api-reference/get-workspace-connector-policies).","example":"googlecalendar"},"builder_connection_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Builder Connection Enabled","description":"Whether builders can connect the connector to the workspace's apps. Leave it out, or send `null`, to keep it.","example":false},"per_user_connection_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Per User Connection Enabled","description":"Whether the apps' users can connect their own accounts through a workspace connector. Turning it on needs a workspace connector for the connector. Leave it out, or send `null`, to keep it.","example":true}},"type":"object","required":["integration_type"],"title":"BulkToggleAction","description":"The switches to set for one connector."},"BulkToggleRequest":{"properties":{"actions":{"items":{"$ref":"#/components/schemas/BulkToggleAction"},"type":"array","maxItems":100,"title":"Actions","description":"The changes, up to 100. An empty list changes nothing.","example":[{"builder_connection_enabled":false,"integration_type":"googlecalendar"},{"builder_connection_enabled":true,"integration_type":"slack","per_user_connection_enabled":true}]}},"type":"object","required":["actions"],"title":"BulkToggleRequest","description":"Policy changes to apply in order."},"BulkToggleResponse":{"properties":{"results":{"items":{"$ref":"#/components/schemas/BulkToggleResult"},"type":"array","title":"Results","description":"One result per action, in the order sent.","example":[{"integration_type":"googlecalendar","item":{"builder_connection_enabled":false,"connector_mode":"all","display_name":"Google Calendar","has_per_user_credentials":false,"integration_type":"googlecalendar","not_yet_enabled":false,"per_user_connection_enabled":false},"ok":true},{"error":"Cannot enable per-user connections without credentials configured.","integration_type":"slack","ok":false}]}},"type":"object","required":["results"],"title":"BulkToggleResponse","description":"The outcome of each action."},"BulkToggleResult":{"properties":{"integration_type":{"type":"string","title":"Integration Type","description":"Connector the action was for.","example":"googlecalendar"},"ok":{"type":"boolean","title":"Ok","description":"Whether the action applied (`true`) or failed (`false`).","example":false},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why the action failed, or `null` when `ok` is `true`. Its wording can change, so don't parse it.","example":"Cannot enable per-user connections without credentials configured."},"item":{"anyOf":[{"$ref":"#/components/schemas/ConnectorGovernanceItem"},{"type":"null"}],"description":"The connector's policy after the action, or `null` when `ok` is `false`.","example":{"builder_connection_enabled":false,"connector_mode":"all","display_name":"Google Calendar","has_per_user_credentials":false,"integration_type":"googlecalendar","not_yet_enabled":false,"per_user_connection_enabled":false}}},"type":"object","required":["integration_type","ok"],"title":"BulkToggleResult","description":"What happened to one action."},"BulkUpdateMemberCreditLimitRequest":{"properties":{"items":{"items":{"$ref":"#/components/schemas/BulkMemberCreditLimitItem"},"type":"array","maxItems":200,"minItems":1,"title":"Items","description":"The credit limits, 1 to 200. Each email can appear once.","example":[{"credit_limit":500,"email":"dana@acme.com"},{"email":"sam@acme.com"}]}},"type":"object","required":["items"],"title":"BulkUpdateMemberCreditLimitRequest","description":"Credit limits to apply in one request."},"BulkUpdateMemberRoleRequest":{"properties":{"items":{"items":{"$ref":"#/components/schemas/BulkMemberRoleItem"},"type":"array","maxItems":200,"minItems":1,"title":"Items","description":"The role changes, 1 to 200. Each email can appear once.","example":[{"email":"dana@acme.com","role":"editor"},{"email":"sam@acme.com","role":"viewer"}]}},"type":"object","required":["items"],"title":"BulkUpdateMemberRoleRequest","description":"Role changes to apply in one request."},"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`, `PAUSED`, `RESUMED`, `DELETED`, `EDITED`, or `EU_POLITICAL_AUTO_DECLARED`.","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 in E.164, empty when the campaign has none. Both channel types store the normalized form Google holds: a call asset on PERFORMANCE_MAX, `smartCampaignSettings.phone_number` 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":"This is `cancelling` when the request was accepted. On a run that had already finished, it's that run's 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"},"CancelTestRunResult":{"properties":{"success":{"type":"boolean","title":"Success","description":"Always `true`.","example":true},"already_terminal":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Already Terminal","description":"`true` when the run had already ended, so nothing changed. Left out when the run was cancelled.","example":true}},"type":"object","required":["success"],"title":"CancelTestRunResult","description":"The result of cancelling a run."},"CancelledOwnershipTransfer":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the cancelled ownership transfer.","example":"68d4b1e9c2a7f3001e5b8c40"},"status":{"type":"string","title":"Status","description":"Status of the transfer, which is always `cancelled`.","example":"cancelled"}},"type":"object","required":["id","status"],"title":"CancelledOwnershipTransfer","description":"An ownership transfer that was just cancelled."},"ChangeResult":{"properties":{"status":{"type":"string","const":"success","title":"Status","description":"Always `success`.","example":"success"},"message":{"type":"string","title":"Message","description":"Short confirmation. Its wording can change, so don't parse it.","example":"Members added to group."}},"type":"object","required":["status","message"],"title":"ChangeResult","description":"Confirms the change."},"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/StatusObject"},{"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."},"CheckpointBuildError":{"properties":{"message":{"type":"string","title":"Message","description":"What went wrong with the build. When a checkpoint is marked `failed` after waiting too long for another build of the same commit, this is `Timed out waiting for another build of this commit to finish`.","example":"Timed out waiting for another build of this commit to finish"}},"type":"object","required":["message"],"title":"CheckpointBuildError"},"CheckpointFiles":{"properties":{"files":{"additionalProperties":{"anyOf":[{"type":"string"},{"additionalProperties":true,"type":"object"}]},"type":"object","title":"Files","description":"The checkpoint's source files, keyed by path. Pages, components, backend functions, entities and agents are keyed by kind and name with no file extension, such as `pages/Home` or `functions/sendWelcomeEmail`. A few files use a fixed name, such as `layout`. Entity, agent and workflow definitions come back as JSON objects, and source files as text.","example":{"entities/Task":{"name":"Task","properties":{"title":{"type":"string"}},"type":"object"},"functions/sendWelcomeEmail":"Deno.serve(async (req) => Response.json({ ok: true }));\n","pages/Home":"export default function Home() {\n  return <h1>Welcome</h1>;\n}\n"}}},"type":"object","required":["files"],"title":"CheckpointFiles","description":"The source files saved in a checkpoint."},"CheckpointListItem":{"properties":{"checkpoint_id":{"type":"string","title":"Checkpoint Id","description":"ID of the checkpoint. Pass it to [Download data checkpoint](/api-reference/download-data-checkpoint) or [Restore data to checkpoint](/api-reference/restore-data-to-checkpoint).","example":"68a1c2d3e4f5a6b7c8d9e0f1"},"entity_name":{"type":"string","title":"Entity Name","description":"Entity the checkpoint belongs to.","example":"Invoice"},"data_as_of":{"type":"string","title":"Data As Of","description":"The moment the checkpoint marks, as a UTC timestamp in ISO 8601 format with a `Z` suffix. The checkpoint holds the entity's data as it was just before this moment, so it leaves out the change that created it.","example":"2026-06-04T14:07:02.115000Z"},"trigger_type":{"type":"string","enum":["crud","schema_change","restore"],"title":"Trigger Type","description":"What created the checkpoint. `crud` means a change to a record, `schema_change` means a change to the entity's schema, and `restore` means the backup Base44 saves just before a restore rewrites the entity.","example":"crud"},"restore_batch_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Restore Batch Id","description":"ID of the restore that saved this backup, as `batch_id` from [Restore data to checkpoint](/api-reference/restore-data-to-checkpoint). It is `null` on every checkpoint that isn't a restore backup, and on backups saved before Base44 started recording it.","example":"3f1c9a52-7b4e-4d2a-9c61-5e8f0b7a2d14"}},"type":"object","required":["checkpoint_id","entity_name","data_as_of","trigger_type"],"title":"CheckpointListItem","description":"One point in an entity's version history that its data can be restored to."},"CheckpointListPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/CheckpointListItem"},"type":"array","title":"Items","description":"The page's checkpoints, newest first.","example":[{"checkpoint_id":"68a1c2d3e4f5a6b7c8d9e0f1","data_as_of":"2026-06-04T14:07:02.115000Z","entity_name":"Invoice","trigger_type":"crud"}]},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Cursor for fetching the next page. The value is `null` if there are no more pages. A full page always carries one, so the page after it can come back empty.","example":"gAAAAABn7vJ..."}},"type":"object","required":["items"],"title":"CheckpointListPage","description":"One page of an app's version-history checkpoints, newest first."},"CheckpointSnapshotDownload":{"properties":{"fields":{"items":{"type":"string"},"type":"array","title":"Fields","description":"The entity's columns at the checkpoint, in order. That's the fields its schema declared then, followed by the fields Base44 adds to every record. Use them as the header row when you write `records` to a CSV file.","example":["amount","status","customer_email","id","created_date","updated_date","created_by_id","created_by","is_sample"]},"records":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Records","description":"Every record the entity held at the checkpoint, in the same shape as [List entity records](/api-reference/list-entity-records) returns them.","example":[{"amount":4200,"created_by":"jane@acme.com","created_by_id":"6874b0c2e1a94d0031bb77de","created_date":"2026-06-01T09:23:41.481000","customer_email":"jane@acme.com","id":"6886b8d390dc7e2f4a2c91b3","is_sample":false,"status":"draft","updated_date":"2026-06-01T09:23:41.481000"}]}},"type":"object","required":["fields","records"],"title":"CheckpointSnapshotDownload","description":"An entity's records as they stood at a checkpoint."},"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. A `failed` build can be retried with [Retry checkpoint build](/api-reference/retry-checkpoint-build).","example":"ready"},"build_error":{"anyOf":[{"$ref":"#/components/schemas/CheckpointBuildError"},{"type":"null"}],"description":"Why the checkpoint's last preview build failed, or `null` if no failure was recorded. It's cleared when a build succeeds, so while a retry is `building` it can still describe the previous failure.","example":{"message":"Timed out waiting for another build of this commit to finish"}},"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."},"ClaimUrlResponse":{"properties":{"claim_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Claim Url","description":"Stripe onboarding link, or `null` when no link is available. Treat this link as a credential.","example":"https://dashboard.stripe.com/claim/example"},"is_claimable":{"type":"boolean","title":"Is Claimable","description":"Whether the returned link starts a claim. A link that resumes activation of an already claimed sandbox returns `false`.","example":true},"no_link_reason":{"anyOf":[{"type":"string","enum":["already_active","sandbox_expired","unavailable"]},{"type":"null"}],"title":"No Link Reason","description":"Reason no link is returned. `already_active` means the account already finished activation, so there's nothing left to claim. `sandbox_expired` means an unclaimed sandbox passed its 60-day window. `unavailable` covers every other case, including no sandbox at all. Otherwise `null`, when a link is returned. Defaults to `null`.","example":"unavailable"}},"type":"object","required":["claim_url","is_claimable"],"title":"ClaimUrlResponse","description":"Claim URL response."},"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."},"CollaboratorResponse":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the collaborator. Pass it to [Remove collaborator](/api-reference/remove-collaborator). The owner's entry can have the ID `owner`, which isn't a real ID.","example":"68b2d4f6a8c0e2f4a6b8c0d2"},"email":{"type":"string","title":"Email","description":"Email of the collaborator.","example":"jane@acme.com"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name","description":"Full name of the collaborator, or `null` if none is set.","example":"Jane Doe"},"profile_image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Profile Image Url","description":"URL of the collaborator's Base44 profile image, or `null` if they don't have one.","example":"https://lh3.googleusercontent.com/a/ACg8ocJ2x"},"profile_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Profile Slug","description":"Slug of the collaborator's public Base44 builder profile, or `null` if they don't have one.","example":"jane-doe"},"is_owner":{"type":"boolean","title":"Is Owner","description":"Whether the collaborator owns the app.","default":false,"example":false},"is_pending":{"type":"boolean","title":"Is Pending","description":"Whether the collaborator was invited but hasn't opened the app in Base44 yet.","default":false,"example":false},"is_guest":{"type":"boolean","title":"Is Guest","description":"Whether the collaborator is a guest in the app's workspace rather than a member.","default":false,"example":false},"collaborator_source":{"anyOf":[{"type":"string","const":"workspace"},{"type":"null"}],"title":"Collaborator Source","description":"`workspace` when the collaborator joined because the app is open to everyone in its workspace, or `null` when they were invited.","example":"workspace"}},"type":"object","required":["id","email"],"title":"CollaboratorResponse","description":"A person who can edit the app in Base44."},"Collaborators":{"items":{"$ref":"#/components/schemas/CollaboratorResponse"},"type":"array","title":"Collaborators","description":"The app's collaborators."},"CommentAnchor":{"properties":{"source_location":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Source Location","description":"Position of the element's tag in the app's code, as `file:line:column`, exactly as the element's `data-source-location` attribute in the preview. The pin shows only on an element with this value. `null` for a thread with no pin.","example":"pages/Pricing.jsx:42:8"},"element_tag":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Element Tag","description":"HTML tag of the element, such as `button`.","example":"button"},"instance_index":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Instance Index","description":"Which of the elements sharing `source_location` the pin is on, counting from `0` in page order, for an element repeated in a list. `null` means the first.","example":0},"point":{"anyOf":[{"$ref":"#/components/schemas/CommentAnchorPoint"},{"type":"null"}],"description":"Where on the element the pin sits. `null` puts it at the middle of the top edge."},"crop":{"anyOf":[{"$ref":"#/components/schemas/CommentAnchorCrop"},{"type":"null"}],"description":"Area of the page the screenshot shows, used to redraw the crop box in the builder."},"region":{"anyOf":[{"$ref":"#/components/schemas/CommentAnchorRegion"},{"type":"null"}],"description":"Area of the visible preview the screenshot shows when it was taken."},"viewport_size":{"anyOf":[{"$ref":"#/components/schemas/CommentAnchorViewportSize"},{"type":"null"}],"description":"Size of the preview when the screenshot was taken."}},"type":"object","title":"CommentAnchor","description":"Where a comment thread is pinned in the app's preview. When the builder's AI edits the anchored\nfile, Base44 moves an open thread's `source_location` to the element's new line."},"CommentAnchorCrop":{"properties":{"x":{"type":"number","title":"X","description":"Left edge, in page pixels.","example":512},"y":{"type":"number","title":"Y","description":"Top edge, in page pixels, scroll included.","example":1340},"width":{"type":"number","exclusiveMinimum":0.0,"title":"Width","description":"Width, in pixels.","example":240},"height":{"type":"number","exclusiveMinimum":0.0,"title":"Height","description":"Height, in pixels.","example":96}},"type":"object","required":["x","y","width","height"],"title":"CommentAnchorCrop","description":"The screenshot's area in page pixels: the visible area plus the page's scroll when it was taken."},"CommentAnchorPoint":{"properties":{"x":{"type":"number","maximum":1.0,"minimum":0.0,"title":"X","description":"Horizontal position, as a fraction of the element's width.","example":0.5},"y":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Y","description":"Vertical position, as a fraction of the element's height.","example":0.5}},"type":"object","required":["x","y"],"title":"CommentAnchorPoint","description":"Fractions of the anchored element's rect, not of the document."},"CommentAnchorRegion":{"properties":{"x":{"type":"number","maximum":1.0,"minimum":0.0,"title":"X","description":"Left edge, as a fraction of the preview's width.","example":0.42},"y":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Y","description":"Top edge, as a fraction of the preview's height.","example":0.18},"width":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Width","description":"Width, as a fraction of the preview's width.","example":0.2},"height":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Height","description":"Height, as a fraction of the preview's height.","example":0.08}},"type":"object","required":["x","y","width","height"],"title":"CommentAnchorRegion","description":"The screenshot's area as fractions of the visible preview when it was taken, so it depends on the scroll."},"CommentAnchorViewportSize":{"properties":{"width":{"type":"integer","minimum":1.0,"title":"Width","description":"Width of the preview, in pixels.","example":1280},"height":{"type":"integer","minimum":1.0,"title":"Height","description":"Height of the preview, in pixels.","example":800}},"type":"object","required":["width","height"],"title":"CommentAnchorViewportSize","description":"Size of the preview when the screenshot was taken."},"CommentMessage":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the comment.","example":"68e2b7c4d4f0a9001c3e5a1c"},"content":{"type":"string","title":"Content","description":"Text of the comment.","example":"Make this button match the header color."},"sender_id":{"type":"string","title":"Sender Id","description":"ID of the Base44 user who wrote it, or `base44` for a reply from the builder agent.","example":"6820f41be7b91d003c45a20a"},"sender_name":{"type":"string","title":"Sender Name","description":"Name of the author when they wrote it, or their email when they had no name. `Base44` for the builder agent.","example":"Dana Levi"},"sender_avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sender Avatar Url","description":"URL of the author's profile image, or `null` when they have none.","example":"https://lh3.googleusercontent.com/a/ACg8ocJ2"},"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"When the comment was posted, in UTC.","example":"2026-10-05T09:14:22.512000Z"},"edited_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Edited At","description":"When the comment was last edited, in UTC, or `null` when it never was.","example":"2026-10-05T09:14:22.512000Z"},"reactions":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"type":"object","title":"Reactions","description":"Each emoji reacted with, mapped to the IDs of the users who reacted with it.","example":{"👍":["6820f41be7b91d003c45a20a"]}}},"type":"object","required":["id","content","sender_id","sender_name","sender_avatar_url","created_date","edited_at","reactions"],"title":"CommentMessage","description":"One comment: the first comment of a thread or a reply."},"CommentThread":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the thread.","example":"68e2b7c1d4f0a9001c3e5a17"},"page_path":{"type":"string","title":"Page Path","description":"Path of the app page the thread is on.","example":"/pricing"},"anchor":{"$ref":"#/components/schemas/CommentAnchor","description":"Where the thread is pinned in the app's preview."},"screenshot_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Screenshot Url","description":"Signed link to the screenshot attached to the thread, valid for one hour. `null` when the thread has no screenshot or it can't be read.","example":"https://static.base44.com/images/private/6820f3a4e7b91d003c45a1f2/4b1e9c2a7_shot.png?token=eyJhbGciOi"},"resolved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Resolved At","description":"When the thread was resolved, in UTC, or `null` while it's open.","example":"2026-10-05T09:14:22.512000Z"},"message_count":{"type":"integer","title":"Message Count","description":"Number of replies, not counting the first comment.","example":2},"last_activity_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Activity At","description":"When the thread was created or last replied to, in UTC.","example":"2026-10-05T09:14:22.512000Z"},"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"When the thread was created, in UTC.","example":"2026-10-05T09:14:22.512000Z"},"agent_working_since":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Agent Working Since","description":"When the builder agent started working on the thread, in UTC, or `null` when it isn't working on it. A value older than an hour is left over from a turn that stopped.","example":"2026-10-05T09:14:22.512000Z"},"unread":{"type":"boolean","title":"Unread","description":"Whether the thread has a comment from someone else posted after you last read it. Always `false` in the responses of Create comment and Update comment anchor.","example":true}},"type":"object","required":["id","page_path","anchor","screenshot_url","resolved_at","message_count","last_activity_at","created_date","agent_working_since","unread"],"title":"CommentThread","description":"A comment thread's state."},"CommentThreadItem":{"properties":{"thread":{"$ref":"#/components/schemas/CommentThread","description":"The thread's state."},"comment":{"$ref":"#/components/schemas/CommentMessage","description":"The first comment, which started the thread."},"replies":{"items":{"$ref":"#/components/schemas/CommentMessage"},"type":"array","title":"Replies","description":"Replies, oldest first.","example":[{"content":"Done, it now uses the header blue.","created_date":"2026-10-05T09:14:22.512000Z","id":"68e2b80fd4f0a9001c3e5a21","reactions":{},"sender_id":"base44","sender_name":"Base44"}]},"reactor_names":{"additionalProperties":{"type":"string"},"type":"object","title":"Reactor Names","description":"User ID mapped to name, for the people who reacted anywhere in the thread.","example":{"6820f41be7b91d003c45a20a":"Dana Levi"}}},"type":"object","required":["thread","comment","replies","reactor_names"],"title":"CommentThreadItem","description":"A comment thread with all its comments."},"CommentThreadList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/CommentThreadItem"},"type":"array","title":"Items","description":"Threads on this page, newest first.","example":[{"comment":{"content":"Make this button match the header color.","created_date":"2026-10-05T09:14:22.512000Z","id":"68e2b7c4d4f0a9001c3e5a1c","reactions":{},"sender_id":"6820f41be7b91d003c45a20a","sender_name":"Dana Levi"},"reactor_names":{},"replies":[],"thread":{"anchor":{"element_tag":"button","source_location":"pages/Pricing.jsx:42:8"},"created_date":"2026-10-05T09:14:22.512000Z","id":"68e2b7c1d4f0a9001c3e5a17","last_activity_at":"2026-10-05T09:14:22.512000Z","message_count":0,"page_path":"/pricing","unread":true}}]},"has_more":{"type":"boolean","title":"Has More","description":"Whether more threads follow this page.","example":false},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass as `cursor` to get the next page. `null` on the last page.","example":"gAAAAABn7vJ0q2kX"}},"type":"object","required":["items","has_more","next_cursor"],"title":"CommentThreadList","description":"One page of an app's comment threads."},"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."},"ConfigureLiveKeysRequest":{"properties":{"publishable_key":{"type":"string","title":"Publishable Key","description":"Live Stripe publishable key beginning with `pk_live_`.","example":"pk_live_example"},"secret_key":{"type":"string","title":"Secret Key","description":"Live Stripe secret or restricted key beginning with `sk_live_` or `rk_live_`.","example":"sk_live_example"},"webhook_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Secret","description":"Live webhook signing secret beginning with `whsec_`. Defaults to `null`, which attempts automatic migration when an existing webhook secret is present.","example":"whsec_example"}},"type":"object","required":["publishable_key","secret_key"],"title":"ConfigureLiveKeysRequest","description":"Request to configure live mode API keys."},"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":{"anyOf":[{"type":"string"},{"type":"null"}],"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). Provide either this or `workspace_installation_id`.","example":"58231904"},"workspace_installation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Installation Id","description":"ID of a GitHub installation connected to the app's workspace, as `id` in [List workspace GitHub installations](/api-reference/list-workspace-github-installations). Provide either this or `installation_id`."},"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"],"title":"ConnectRepoRequest","description":"Request to connect a repository.\n\nExactly one of `installation_id` or `workspace_installation_id` must be provided."},"ConnectedDomain":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the custom domain.","example":"68c0d7e2b5a1f4001d2e9b37"},"domain":{"type":"string","title":"Domain","description":"The domain name.","example":"crm.acme.com"},"disabled":{"type":"boolean","title":"Disabled","description":"Whether the domain is connected but turned off.","example":false}},"type":"object","required":["id","domain","disabled"],"title":"ConnectedDomain"},"ConnectionConfigField":{"properties":{"name":{"type":"string","title":"Name","description":"Key to send the value under in `connection_config`.","example":"subdomain"},"display_name":{"type":"string","title":"Display Name","description":"Label for the value.","default":"","example":"Snowflake account subdomain"},"description":{"type":"string","title":"Description","description":"Help text that explains where to find the value.","default":"","example":"The part of your Snowflake URL before .snowflakecomputing.com."},"placeholder":{"type":"string","title":"Placeholder","description":"Sample value to show in an empty input.","default":"","example":"acme-prod"},"required":{"type":"boolean","title":"Required","description":"Whether the value has to be sent (`true`) or can be left out (`false`).","default":true,"example":true},"default_value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Value","description":"Value used when the field is left out, or `null` if there is none.","example":"login"},"validation_pattern":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Validation Pattern","description":"Regular expression the whole value has to match, or `null` if any value is accepted.","example":"^[a-zA-Z0-9-]+$"},"validation_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Validation Error","description":"Message returned when the value doesn't match `validation_pattern`, or `null` for the default message.","example":"Use letters, digits, and hyphens only."}},"type":"object","required":["name"],"title":"ConnectionConfigField","description":"Schema for a connection configuration field."},"ConnectionConfigResponse":{"properties":{"fields":{"items":{"$ref":"#/components/schemas/ConnectionConfigField"},"type":"array","title":"Fields","description":"Values to collect and send as `connection_config`. Empty when the connector needs none.","example":[{"description":"The part of your Snowflake URL before .snowflakecomputing.com.","display_name":"Snowflake account subdomain","name":"subdomain","placeholder":"acme-prod","required":true,"validation_error":"Use letters, digits, and hyphens only.","validation_pattern":"^[a-zA-Z0-9-]+$"}]},"connection_config":{"additionalProperties":{"type":"string"},"type":"object","title":"Connection Config","description":"Values saved on the app's current connection, keyed by field `name`. Empty when there's no connection.","example":{"subdomain":"acme-prod"}}},"type":"object","required":["fields","connection_config"],"title":"ConnectionConfigResponse","description":"The values a connector needs before authorization, and the app's saved ones."},"ConnectionStatus":{"properties":{"connection_mode":{"anyOf":[{"$ref":"#/components/schemas/GitHubConnectionMode"},{"type":"null"}],"description":"Workspace GitHub write model; null when not connected."},"user_github_connected":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"User Github Connected","description":"Whether the current user has a linked GitHub account; computed only in per_user mode."},"can_push":{"type":"boolean","title":"Can Push","description":"Whether the connection permits a push attempt under the workspace mode. Failed user permission lookups allow an attempt; GitHub enforces access.","default":false},"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"},"previous_repository":{"anyOf":[{"$ref":"#/components/schemas/PreviousRepository"},{"type":"null"}],"description":"The repository a user disconnected the app from, which [Reconnect a GitHub repository](/api-reference/reconnect-a-github-repository) can bring back, or `null` when the app is connected, never connected, or lost its repository some other way."}},"type":"object","required":["connected"],"title":"ConnectionStatus","description":"Current connection status for an app."},"ConnectorGovernanceItem":{"properties":{"integration_type":{"type":"string","title":"Integration Type","description":"ID of the connector.","example":"googlecalendar"},"display_name":{"type":"string","title":"Display Name","description":"Name of the connector.","example":"Google Calendar"},"connector_mode":{"type":"string","title":"Connector Mode","description":"Which ways the connector can be connected. One of `all`, `shared_only`, `app_user_only`, `byo_shared_only`, or `byo_shared_and_app_user`, with the same meaning as `connector_mode` in [List connectors](/api-reference/list-connectors).","example":"all"},"builder_connection_enabled":{"type":"boolean","title":"Builder Connection Enabled","description":"Whether the workspace lets builders connect the connector to its apps (`true`) or has turned that off (`false`). When it's `false`, starting a connection is refused.","example":true},"per_user_connection_enabled":{"type":"boolean","title":"Per User Connection Enabled","description":"Whether the apps' users can connect their own accounts with the workspace's credentials (`true`) or not (`false`). Always `false` when `has_per_user_credentials` is `false`.","example":false},"has_per_user_credentials":{"type":"boolean","title":"Has Per User Credentials","description":"Whether the workspace has set up its own OAuth app credentials for the connector.","example":false},"not_yet_enabled":{"type":"boolean","title":"Not Yet Enabled","description":"Whether both switches are off only because the connector became available after the workspace chose to start new connectors off, and nobody has set its policy yet (`true`). Setting either switch makes it `false`.","default":false,"example":false}},"type":"object","required":["integration_type","display_name","connector_mode","builder_connection_enabled","per_user_connection_enabled","has_per_user_credentials"],"title":"ConnectorGovernanceItem"},"ConnectorGovernanceListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ConnectorGovernanceItem"},"type":"array","title":"Items","description":"One entry per connector available to the workspace.","example":[{"builder_connection_enabled":true,"connector_mode":"all","display_name":"Google Calendar","has_per_user_credentials":false,"integration_type":"googlecalendar","per_user_connection_enabled":false}]},"has_any_governance_policies":{"type":"boolean","title":"Has Any Governance Policies","description":"Whether the workspace has restricted any connector (`true`) or left them all at the defaults (`false`).","example":false}},"type":"object","required":["items","has_any_governance_policies"],"title":"ConnectorGovernanceListResponse","description":"The workspace's connector policies, as they apply to the app."},"ConnectorMode":{"type":"string","enum":["all","shared_only","app_user_only","byo_shared_only","byo_shared_and_app_user"],"title":"ConnectorMode","description":"Which ways a connector can be connected."},"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"},"ConversationAgentSummary":{"properties":{"agent_name":{"type":"string","title":"Agent Name","description":"Name of the agent.","example":"support_agent"},"last_message_preview":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Message Preview","description":"First 100 characters of the latest message in the user's newest conversation with this agent.","example":"Your order ships tomorrow and should arrive by Friday."},"last_message_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Message Time","description":"When the user's newest conversation with this agent was last updated, as a UTC timestamp in ISO 8601 format.","example":"2026-01-15T09:31:07"},"conversation_count":{"type":"integer","title":"Conversation Count","description":"Number of conversations the user has with this agent.","default":0,"example":2},"message_count":{"type":"integer","title":"Message Count","description":"Number of messages across the user's conversations with this agent.","default":0,"example":6},"credit_count":{"type":"number","title":"Credit Count","description":"Credits charged for this agent's replies across the user's conversations with it.","default":0,"example":1.5},"conversations":{"items":{"$ref":"#/components/schemas/ConversationSummary"},"type":"array","title":"Conversations","description":"Up to 100 of the user's conversations with this agent."}},"type":"object","required":["agent_name"],"title":"ConversationAgentSummary"},"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."},"ConversationDetailResponse":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the conversation.","example":"68a1d2c3e4f5061728394a5b"},"app_id":{"type":"string","title":"App Id","description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"},"agent_name":{"type":"string","title":"Agent Name","description":"Name of the agent the conversation is with.","example":"support_agent"},"created_by_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Id","description":"ID of the app user who started the conversation. An anonymous visitor's conversation carries `anonymous`, `guest`, an empty string, or `null` instead.","example":"68a1d2c3e4f5061728394a6c"},"created_by_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Email","description":"Email of the app user who started the conversation, or `null` for an anonymous visitor or an app user who no longer exists.","example":"jane@acme.com"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Free-form data stored with the conversation. It holds whatever the app set when it created the conversation, plus channel details such as a WhatsApp user's phone number.","example":{"name":"Order #1042 delivery date"}},"messages":{"items":{},"type":"array","title":"Messages","description":"Up to the 200 most recent messages, oldest first. Each message has a `role` of `user` or `assistant` and its `content`. An assistant message also lists the `tool_calls` the agent made and what each one returned, which can include records from the app's data.","example":[{"content":"When will order #1042 arrive?","hidden":false,"id":"4f1c2a9e-7b3d-4e8a-9c21-5d6f7a8b9c0d","role":"user"},{"content":"Your order ships tomorrow and should arrive by Friday.","hidden":false,"id":"8a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d","role":"assistant"}]},"message_count":{"type":"integer","title":"Message Count","description":"Number of messages in the conversation.","default":0,"example":6},"credit_count":{"type":"number","title":"Credit Count","description":"Credits charged for the agent's replies in the conversation.","default":0,"example":1.5},"created_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created Date","description":"When the conversation was created, as a UTC timestamp in ISO 8601 format.","example":"2026-01-15T09:23:41"},"updated_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated Date","description":"When the conversation was last updated, as a UTC timestamp in ISO 8601 format.","example":"2026-01-15T09:31:07"}},"type":"object","required":["id","app_id","agent_name"],"title":"ConversationDetailResponse","description":"One conversation with one of the app's agents, with its messages."},"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"},"ConversationSummary":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the conversation.","example":"68a1d2c3e4f5061728394a5b"},"app_id":{"type":"string","title":"App Id","description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"},"agent_name":{"type":"string","title":"Agent Name","description":"Name of the agent the conversation is with.","example":"support_agent"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Title of the conversation, or `null` if it doesn't have one.","example":"Order #1042 delivery date"},"created_by_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Id","description":"ID of the app user who started the conversation. An anonymous visitor's conversation carries `anonymous`, `guest`, an empty string, or `null` instead.","example":"68a1d2c3e4f5061728394a6c"},"created_by_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Email","description":"Email of the app user who started the conversation, or `null` for an anonymous visitor or an app user who no longer exists.","example":"jane@acme.com"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Free-form data stored with the conversation. It holds whatever the app set when it created the conversation, plus channel details such as a WhatsApp user's phone number.","example":{"name":"Order #1042 delivery date"}},"created_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created Date","description":"When the conversation was created, as a UTC timestamp in ISO 8601 format.","example":"2026-01-15T09:23:41"},"updated_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated Date","description":"When the conversation was last updated, as a UTC timestamp in ISO 8601 format.","example":"2026-01-15T09:31:07"},"message_count":{"type":"integer","title":"Message Count","description":"Number of messages in the conversation.","default":0,"example":6},"credit_count":{"type":"number","title":"Credit Count","description":"Credits charged for the agent's replies in the conversation.","default":0,"example":1.5},"first_message_preview":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Message Preview","description":"First 100 characters of the conversation's title, or of its first visible message when it doesn't have a title.","example":"Order #1042 delivery date"},"last_message_preview":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Message Preview","description":"First 100 characters of the conversation's latest message.","example":"Your order ships tomorrow and should arrive by Friday."},"last_message_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Message Time","description":"When the conversation was last updated, as a UTC timestamp in ISO 8601 format.","example":"2026-01-15T09:31:07"}},"type":"object","required":["id","app_id","agent_name"],"title":"ConversationSummary"},"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."},"ConversationUserSummary":{"properties":{"user_group_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Group Key","description":"Key the user's conversations are grouped under. It's the app user's ID, `anonymous:` followed by the visitor ID for an anonymous visitor, or `__anonymous_visitors__` for anonymous conversations without a visitor ID.","example":"68a1d2c3e4f5061728394a6c"},"created_by_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Id","description":"ID of the app user who started the conversation. An anonymous visitor's conversation carries `anonymous`, `guest`, an empty string, or `null` instead.","example":"68a1d2c3e4f5061728394a6c"},"created_by_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Email","description":"Email of the app user who started the conversation, or `null` for an anonymous visitor or an app user who no longer exists.","example":"jane@acme.com"},"created_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Name","description":"Full name of the app user, or `null` if it isn't set. An anonymous visitor gets a generated display name instead.","example":"Jane Cooper"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"The app user's role in the app, such as `user` or `admin`. It's `guest` for an anonymous visitor, or `null` if the app user no longer exists.","example":"user"},"is_anonymous_user":{"type":"boolean","title":"Is Anonymous User","description":"`true` for an anonymous visitor, and `false` for a signed-in app user.","default":false,"example":false},"anonymous_visitor_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Anonymous Visitor Id","description":"ID that identifies an anonymous visitor across conversations. Read it only when `is_anonymous_user` is `true`.","example":"v_8f3k2m9q"},"agent_names":{"items":{"type":"string"},"type":"array","title":"Agent Names","description":"Names of the agents the user has conversations with.","example":["support_agent"]},"agent_count":{"type":"integer","title":"Agent Count","description":"Number of agents the user has conversations with.","default":0,"example":1},"conversation_count":{"type":"integer","title":"Conversation Count","description":"Number of conversations the user has with the app's agents.","default":0,"example":2},"message_count":{"type":"integer","title":"Message Count","description":"Number of messages across the user's conversations.","default":0,"example":6},"credit_count":{"type":"number","title":"Credit Count","description":"Credits charged for the agents' replies across the user's conversations.","default":0,"example":1.5},"latest_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Latest Time","description":"When the user's newest conversation was last updated, as a UTC timestamp in ISO 8601 format.","example":"2026-01-15T09:31:07"},"agents":{"items":{"$ref":"#/components/schemas/ConversationAgentSummary"},"type":"array","title":"Agents","description":"The user's conversations broken down by agent."},"conversations":{"items":{"$ref":"#/components/schemas/ConversationSummary"},"type":"array","title":"Conversations","description":"Up to 100 of the user's conversations across all of the app's agents."}},"type":"object","title":"ConversationUserSummary"},"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."},"CoreIntegrationProtectionEnabled":{"properties":{"protected":{"type":"boolean","title":"Protected","description":"Always `true`. The published app now enforces the protection.","example":true}},"type":"object","required":["protected"],"title":"CoreIntegrationProtectionEnabled","description":"Confirms core integration protection is on."},"CpcEstimateRequest":{"properties":{"landing_page":{"type":"string","title":"Landing Page","description":"Page the campaign sends clicks to, as an `http` or `https` URL. Google derives the keyword ideas it prices from this page. Required: anything else is rejected with a 400.","default":"","example":"https://example.com/furniture"},"geo_targets":{"items":{"type":"string"},"type":"array","title":"Geo Targets","description":"Locations to price clicks in, as Google geo target constant IDs (`geoTargetConstants/1023191`, or just `1023191`). When you send more than 10, only 10 of them are priced, always the same 10 for the same set. Leave it empty to price all locations. If Google's keyword planner rejects the locations you send, the estimate covers all locations instead of failing.","example":["1023191"]},"language_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language Code","description":"Two-letter code of the language the ads run in, which picks the keyword market. A regional tag like `pt-BR` is reduced to `pt`, and a missing or unsupported code prices in English. [List Google Ads advertising languages](/api-reference/list-google-ads-advertising-languages) returns the supported codes.","example":"en"}},"type":"object","title":"CpcEstimateRequest"},"CreateAppCommentPayload":{"properties":{"content":{"type":"string","maxLength":5000,"minLength":1,"title":"Content","description":"Text of the comment, 1 to 5000 characters.","example":"Make this button match the header color."},"page_path":{"type":"string","maxLength":512,"title":"Page Path","description":"Path of the app page the comment is about. Defaults to `/`.","default":"/","example":"/pricing"},"anchor":{"$ref":"#/components/schemas/CommentAnchor","description":"Where to pin the thread in the app's preview. Leave it out for a thread with no pin."},"screenshot_file_uri":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Screenshot File Uri","description":"`file_uri` of a screenshot uploaded to this app with [Upload app file](/api-reference/upload-app-file) and `visibility` set to `private`. Starts with `mp/private/` followed by the app's ID.","example":"mp/private/6820f3a4e7b91d003c45a1f2/4b1e9c2a7_shot.png"},"mentioned_emails":{"items":{"type":"string"},"type":"array","maxItems":50,"title":"Mentioned Emails","description":"Emails of up to 50 people the comment mentions. Mentions send no notification.","example":["dana@acme.com"]}},"type":"object","required":["content"],"title":"CreateAppCommentPayload","description":"A new comment thread."},"CreateAppFolderRequest":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name","description":"Name of the folder, 1 to 100 characters. Names don't have to be unique.","example":"Client projects"},"scope":{"type":"string","enum":["workspace","personal"],"title":"Scope","description":"`workspace` for a folder every member of the workspace sees, or `personal` for one only you see.","example":"workspace"},"parent_folder_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Folder Id","description":"ID of the folder to create this one in, or `null` for a top-level folder. The parent must have the same `scope` and `app_type`, and a personal parent must be yours.","example":"68c1f27eb4e7d3005a2c9e0f"},"position":{"type":"number","title":"Position","description":"Sort key among folders. Lower values come first.","default":0,"example":2.0},"color":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Color","description":"Color for the folder, up to 32 characters, such as a hex color.","example":"#3B82F6"},"icon":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Icon","description":"Icon name for the folder, up to 64 characters.","example":"briefcase"},"app_type":{"anyOf":[{"type":"string","enum":["user_app","user_agent"]},{"type":"null"}],"title":"App Type","description":"`user_app` for a folder of builder apps, or `user_agent` for a folder of agents. Defaults to `user_app`.","example":"user_app"}},"type":"object","required":["name","scope"],"title":"CreateAppFolderRequest"},"CreateBranchRequest":{"properties":{"branch_name":{"anyOf":[{"type":"string","maxLength":100,"minLength":1},{"type":"null"}],"title":"Branch Name","description":"Name of the branch. It starts with a letter or digit and contains only letters, digits, `.`, `_`, `/` and `-`. It can't contain `..` or `//`, end with `/` or `.`, start with `b44/`, or be `main`, and no part between slashes can start with `.` or end with `.lock`. Leave it out to have Base44 name the branch from `prompt`.","example":"add-contact-form"},"prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt","description":"Text to name the branch from when you leave out `branch_name`. Base44 generates a short name from it. An empty string names the branch after your first name and today's date.","example":"Add a contact form to the home page"},"from_message_id":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"From Message Id","description":"ID of a message in main's conversation, from [Read conversation messages](/api-reference/read-conversation-messages). The branch starts from the app as it was when that message was sent, instead of from main's latest state. Without `branch_name`, the branch is named after the last user message before that one, and `prompt` is ignored.","example":"7f3a1c88-52d4-4a0e-9b31-2c6f0d8e4a19"}},"type":"object","title":"CreateBranchRequest","description":"The new branch's name, or text to name it from."},"CreateCheckpointResult":{"properties":{"checkpoint_id":{"type":"string","title":"Checkpoint Id","description":"ID of the checkpoint. Pass it as `checkpoint_id` to [Restore checkpoint](/api-reference/restore-checkpoint) to come back to this state.","example":"6886b8d390dc7e2f4a2c91b3"},"name":{"type":"string","title":"Name","description":"The checkpoint's title, either the `name` you sent or one Base44 generated.","example":"Before refactoring the dashboard"},"git_commit_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Git Commit Hash","description":"The commit this checkpoint points at, or `null` when the app has no committed sandbox revision yet. A checkpoint without one still restores the app state Base44 captured.","example":"4f9c1a7e8b2d3c5f6a0b1e2d3c4f5a6b7c8d9e0f"}},"type":"object","required":["checkpoint_id","name"],"title":"CreateCheckpointResult","description":"The checkpoint that was created."},"CreateConversationPayload":{"properties":{"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Metadata to store on the conversation when this call creates it. Ignored when you already have a conversation with the agent.","example":{"source":"crm-sync"}}},"type":"object","title":"CreateConversationPayload"},"CreateDomainBody":{"properties":{"domain":{"type":"string","title":"Domain","description":"The domain to add. A scheme, a trailing slash, and a leading `www.` are removed, and it's stored in lower case.","example":"example.com"}},"type":"object","required":["domain"],"title":"CreateDomainBody"},"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"},"CreateFlowRequest":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name","description":"Short name for the test.","example":"Create a project"},"goal":{"type":"string","maxLength":500,"minLength":1,"title":"Goal","description":"What the testing agent should try to do, in plain language.","example":"Sign up, create a project called Launch, and check that it appears on the dashboard."},"role":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Role","description":"Role to run the test as, such as `admin`. Leave it out to let Base44 pick one from the name or goal.","example":"admin"}},"type":"object","required":["name","goal"],"title":"CreateFlowRequest"},"CreateGroupRequest":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name","description":"Name of the group, up to 100 characters. Must be unique among the workspace's custom groups, ignoring case.","example":"Design team"},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Description","description":"Short description of the group's purpose, up to 500 characters.","example":"Product designers"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"Role every member gets: `admin`, `editor`, `viewer`, or `no_access` to block members who get their role only from this group. Leave it out, or send `null`, for a group that grants no role.","example":"editor"},"member_emails":{"items":{"type":"string"},"type":"array","maxItems":200,"title":"Member Emails","description":"Emails of active workspace members to add, up to 200.","example":["dana@acme.com","sam@acme.com"]}},"type":"object","required":["name"],"title":"CreateGroupRequest"},"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"},"CreateOrganizationConnectorRequest":{"properties":{"integration_type":{"anyOf":[{"type":"string","enum":["googlecalendar","google_classroom","googledrive","gmail","googlesheets","googledocs","googleslides","googlebigquery","googlemeet","googletasks","googleads","google_analytics","google_search_console","slack","notion","salesforce","hubspot","linkedin","tiktok","instagram","discord","slackbot","wix","github","gitlab","bamboohr","dropbox","clickup","wrike","box","outlook","linear","airtable","microsoft_teams","share_point","one_drive","typeform","splitwise","hugging_face","calendly","contentful","supabase","snowflake","databricks","quickbooks","square"]},{"type":"string","maxLength":80,"minLength":1,"pattern":"^[a-z0-9_]+$"}],"title":"Integration Type","description":"Connector to create it for, from `integration_type` in [List connectors](/api-reference/list-connectors). Its `connector_mode` has to be one of `all`, `app_user_only`, `byo_shared_only`, or `byo_shared_and_app_user`.","example":"googlecalendar"},"name":{"type":"string","minLength":1,"title":"Name","description":"Name of the workspace connector. Must be unique in the workspace, matching case exactly.","example":"Sales Google Calendar"},"credential_source":{"type":"string","enum":["byo","base44"],"description":"Whose OAuth app the connector runs on: `byo` for your own, registered in the provider's console, or `base44` for Base44's. Defaults to `byo`. `base44` needs a plan that includes Base44-managed connectors, and only some connectors offer it.","default":"byo","example":"byo"},"client_id":{"type":"string","maxLength":512,"title":"Client Id","description":"Client ID of your OAuth app, up to 512 characters. Required with `byo`. Leave it out with `base44`.","default":"","example":"1234567890-abc123def456.apps.googleusercontent.com"},"client_secret":{"type":"string","maxLength":512,"title":"Client Secret","description":"Client secret of your OAuth app, up to 512 characters. Required with `byo`, except for connectors whose OAuth apps have no secret, such as Linear. Leave it out with `base44`. It's stored encrypted and never returned.","default":"","example":"your-oauth-client-secret"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"OAuth scopes connections through this connector ask for. With `base44`, only scopes Base44's OAuth app is approved for are accepted.","example":["https://www.googleapis.com/auth/calendar.readonly"]},"connection_config":{"additionalProperties":{"type":"string"},"type":"object","title":"Connection Config","description":"Values the connector needs, keyed by the `name` of each of its connection fields. Leave it out for connectors that have none.","example":{}}},"type":"object","required":["integration_type","name"],"title":"CreateOrganizationConnectorRequest","description":"A workspace connector to create."},"CreatePriceRequest":{"properties":{"product_id":{"type":"string","title":"Product Id","description":"Stripe product ID to price.","example":"prod_T7mK2p9Q4r6S8v"},"unit_amount":{"type":"integer","title":"Unit Amount","description":"Amount in the currency's smallest unit. For USD, `2500` represents $25.00.","example":2500},"currency":{"type":"string","title":"Currency","description":"Three-letter ISO 4217 currency code. Converted to lowercase before sending to Stripe. Defaults to `usd`.","default":"usd","example":"usd"},"recurring_interval":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recurring Interval","description":"Billing interval. Use `day`, `week`, `month`, or `year`. Omit it or send `null` for a one-time price. Defaults to `null`.","example":"month"},"recurring_interval_count":{"type":"integer","title":"Recurring Interval Count","description":"Number of billing intervals between charges. Used only when a recurring interval is supplied. Defaults to 1.","default":1,"example":1},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Metadata to store, as string key-value pairs. [Stripe's metadata rules](https://docs.stripe.com/api/metadata) limit it to 50 keys, 40 characters per key, and 500 characters per value. Defaults to `null`.","example":{"tier":"standard"}}},"type":"object","required":["product_id","unit_amount"],"title":"CreatePriceRequest","description":"Request to create a Stripe price."},"CreateProductRequest":{"properties":{"name":{"type":"string","title":"Name","description":"Product name.","example":"Studio membership"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Product description. Defaults to `null`.","example":"Access to weekly classes."},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Metadata to store, as string key-value pairs. [Stripe's metadata rules](https://docs.stripe.com/api/metadata) limit it to 50 keys, 40 characters per key, and 500 characters per value. Defaults to `null`.","example":{"tier":"standard"}}},"type":"object","required":["name"],"title":"CreateProductRequest","description":"Request to create a Stripe product."},"CreateShareResult":{"properties":{"group_id":{"type":"string","title":"Group Id","description":"ID of the group, as you sent it.","example":"68c9a0f1d2e4b5001f3a7c90"},"success":{"type":"boolean","title":"Success","description":"Whether the app is now shared with the group by this call.","example":true},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why the group wasn't shared, or `null` on success. `group_not_found` means the ID isn't a group in the app's workspace, and `already_shared` means the app is already shared with it.","example":"already_shared"}},"type":"object","required":["group_id","success","error"],"title":"CreateShareResult"},"CreateSharesRequest":{"properties":{"group_ids":{"items":{"type":"string"},"type":"array","maxItems":50,"minItems":1,"title":"Group Ids","description":"IDs of the workspace groups to share the app with, 1 to 50. A repeated ID counts once.","example":["68c9a0f1d2e4b5001f3a7c90"]},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"App role every member of these groups gets, one of the roles defined on the app's User entity. Leave it out, or send `null`, to assign no role, so members get the default `user` role.","example":"user"}},"type":"object","required":["group_ids"],"title":"CreateSharesRequest"},"CreateSharesResponse":{"properties":{"results":{"items":{"$ref":"#/components/schemas/CreateShareResult"},"type":"array","title":"Results","description":"One result per group, in the order you sent them.","example":[{"group_id":"68c9a0f1d2e4b5001f3a7c90","success":true}]}},"type":"object","required":["results"],"title":"CreateSharesResponse"},"CreateSkillRequest":{"properties":{"name":{"type":"string","maxLength":64,"minLength":1,"title":"Name","description":"Name of the skill, up to 64 characters: lowercase letters and digits in words joined by single hyphens, such as `code-review`. Unique within the workspace.","example":"brand-voice"},"description":{"type":"string","maxLength":1024,"minLength":1,"title":"Description","description":"When the skill applies, up to 1024 characters. The AI builder reads it to decide when to load the skill, so say what the skill is for.","example":"Use when writing any user-facing copy for our apps."},"body":{"type":"string","maxLength":15000,"minLength":1,"title":"Body","description":"The instructions the AI builder follows once it loads the skill, up to 15000 characters.","example":"Write in a warm, plain-spoken voice. Use sentence case for headings and keep buttons to two words."}},"type":"object","required":["name","description","body"],"title":"CreateSkillRequest"},"CreateSuperagent":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name","description":"Name of the agent. Leading and trailing spaces are removed.","example":"Sales assistant"},"description":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Description","description":"Short description of what the agent is for.","example":"Answers questions about our weekly sales."},"logo_url":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Logo Url","description":"URL of the agent's logo image.","example":"https://cdn.acme.com/sales-assistant.png"}},"additionalProperties":false,"type":"object","required":["name"],"title":"CreateSuperagent"},"CreateWebhookPayload":{"properties":{"target_url":{"type":"string","title":"Target Url","description":"Public HTTPS URL that receives the events.","example":"https://example.com/hooks/agent"},"events":{"items":{"type":"string"},"type":"array","title":"Events","description":"Events to receive. One or more of `message.created` and `message.completed`.","example":["message.completed"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Your label for the webhook.","example":"CRM sync"},"generate_secret":{"type":"boolean","title":"Generate Secret","description":"`true` makes Base44 generate an HMAC-SHA256 signing secret and return it once in the response. Deliveries are signed with an `X-Base44-Signature` header only when the webhook has a secret.","default":false,"example":true}},"type":"object","required":["target_url"],"title":"CreateWebhookPayload"},"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."},"CustomizedApplicationEmail":{"properties":{"template_id":{"type":"string","title":"Template Id","description":"ID of the template. Use it with [Get email template](/api-reference/get-email-template).","example":"68c1f0a2b3d4e5f6a7b8c9d0"},"name":{"type":"string","title":"Name","description":"Name of the template: `PasswordReset`, `EmailVerification`, `UserInvite`, or `AccessApproved`.","example":"PasswordReset"},"created":{"type":"boolean","title":"Created","description":"`true` when this request created the template, and `false` when the app already had it.","example":true}},"type":"object","required":["template_id","name","created"],"title":"CustomizedApplicationEmail","description":"The app's own template for an application email."},"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."},"DashboardUrlResponse":{"properties":{"redirect_url":{"type":"string","title":"Redirect Url","description":"One-time Wix dashboard link. Treat it as a credential and open it in a browser.","example":"https://manage.wix.com/example-dashboard-link"}},"type":"object","required":["redirect_url"],"title":"DashboardUrlResponse"},"DateBucket":{"properties":{"field":{"type":"string","title":"Field","description":"Date field to bucket by: `created_date`, `updated_date`, or a field the entity's schema declares as a string with format `date` or `date-time`.","example":"created_date"},"unit":{"type":"string","enum":["day","week","month","year"],"title":"Unit","description":"Length of each period. `week` works only on `created_date` and `updated_date`, where weeks start on Sunday.","default":"day","example":"month"}},"additionalProperties":false,"type":"object","required":["field"],"title":"DateBucket","description":"Groups records by the period a date falls in."},"DeleteAppFolderResult":{"properties":{"deleted":{"type":"boolean","title":"Deleted","description":"Always `true`.","example":true},"mode":{"type":"string","enum":["reparent","cascade"],"title":"Mode","description":"The `mode` the folder was deleted with.","example":"reparent"}},"type":"object","required":["deleted","mode"],"title":"DeleteAppFolderResult"},"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."},"DeleteIssueRequest":{"properties":{"title":{"type":"string","maxLength":400,"title":"Title","description":"The issue's `title`, as the report shows it. Case and punctuation don't matter.","example":"Project doesn't appear after creation"},"reason":{"type":"string","maxLength":200,"title":"Reason","description":"Why the issue isn't a real problem. Stored for your records only.","default":"","example":"The dashboard refreshes on a timer, so this is expected."}},"type":"object","required":["title"],"title":"DeleteIssueRequest"},"DeleteSecretResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Always `true`. A delete that doesn't happen returns an error instead.","example":true}},"type":"object","required":["success"],"title":"DeleteSecretResponse","description":"Confirmation that the secret was deleted."},"DeletedApp":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the deleted app.","example":"6820f3a4e7b91d003c45a1f2"},"name":{"type":"string","title":"Name","description":"Display name of the deleted app.","example":"My CRM"},"is_deleted":{"type":"boolean","title":"Is Deleted","description":"Whether the app is in the trash. Always `true` here, because the delete succeeded.","example":true},"deleted_date":{"type":"string","format":"date-time","title":"Deleted Date","description":"Time the app moved to the trash, as a UTC timestamp in ISO 8601 format. The 30 day restore window starts here. Deleting an app that is already in the trash reports the original deletion time.","example":"2026-08-02T14:30:00"},"deleted_resources":{"items":{"type":"string"},"type":"array","title":"Deleted Resources","description":"Kinds of connected resource this delete released for good, which restoring the app doesn't rebuild. Each value is one of `domains`, `integrations`, `automations`, `redirects`, `workflows`, `templates`, `google_ads`, `plaid`, `folders`, `launchpad`, `agent_blobs`, or `partner_agent`. The list is empty when the app had none of them.","example":["domains","integrations"]}},"type":"object","required":["id","name","is_deleted","deleted_date","deleted_resources"],"title":"DeletedApp","description":"The app that moved to the trash."},"DeletedAsset":{"properties":{"success":{"type":"boolean","title":"Success","description":"Always `true`.","example":true}},"type":"object","required":["success"],"title":"DeletedAsset"},"DeletedBranch":{"properties":{"success":{"type":"boolean","title":"Success","description":"Always `true`.","example":true}},"type":"object","required":["success"],"title":"DeletedBranch","description":"Confirms the deletion."},"DeletedEmailTemplate":{"properties":{"deleted":{"type":"string","title":"Deleted","description":"Name of the deleted template.","example":"Welcome"}},"type":"object","required":["deleted"],"title":"DeletedEmailTemplate"},"DeletedResult":{"properties":{"deleted":{"type":"boolean","title":"Deleted","description":"Always `true`.","example":true}},"type":"object","required":["deleted"],"title":"DeletedResult"},"DeletedSuperagentMessage":{"properties":{"message":{"type":"string","const":"Message deleted","title":"Message","description":"Always `Message deleted`.","example":"Message deleted"}},"type":"object","required":["message"],"title":"DeletedSuperagentMessage","description":"Confirms the message is gone."},"DependentTemplate":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the template listing.","example":"68b2f0c1a4d9e7001c3b5a22"},"name":{"type":"string","title":"Name","description":"Name of the template listing.","example":"CRM starter"},"status":{"type":"string","title":"Status","description":"Review status of the listing: `pending`, `approved`, or `rejected`.","example":"approved"},"type":{"type":"string","title":"Type","description":"Who can see the listing: `public` for every Base44 user, or `workspace` for the app's workspace.","example":"workspace"}},"type":"object","required":["id","name","status","type"],"title":"DependentTemplate"},"DeployApprovalRequested":{"properties":{"recipients_notified":{"type":"integer","title":"Recipients Notified","description":"Number of workspace owners and admins sent the request. `0` when `deduped` is `true`.","example":2},"deduped":{"type":"boolean","title":"Deduped","description":"Whether the call was a repeat of your request from the last 60 seconds, so no one was notified again.","example":false}},"type":"object","required":["recipients_notified","deduped"],"title":"DeployApprovalRequested","description":"The approvers Base44 notified."},"DeployDistResponse":{"properties":{"deployed_at":{"type":"string","title":"Deployed At","description":"When the deploy was recorded, as a UTC timestamp in ISO 8601 format with no offset.","example":"2026-09-30T12:34:56.789000"},"app_url":{"type":"string","title":"App Url","description":"The app's published URL on its Base44 domain. A custom domain isn't returned here.","example":"https://task-tracker.base44.app"}},"type":"object","required":["deployed_at","app_url"],"title":"DeployDistResponse","description":"The deploy that went live."},"DeployPrebuiltSite":{"properties":{"file":{"type":"string","format":"binary","title":"File","description":"The build output as a gzipped tar archive, with a file name ending in `.tar.gz`."}},"type":"object","required":["file"],"title":"DeployPrebuiltSite","description":"A prebuilt site to publish."},"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."},"DeprovisionUserPayload":{"properties":{"email":{"type":"string","format":"email","title":"Email","description":"The app user's email"}},"additionalProperties":false,"type":"object","required":["email"],"title":"DeprovisionUserPayload","description":"Payload for server-to-server app-user deprovisioning (embedded platforms).\n\nThe email travels in the body, not the path: a URL reaches access logs,\nproxies and traces, and an address is personal data."},"DeprovisionUserResponse":{"properties":{"status":{"type":"string","const":"deleted","title":"Status","description":"Always `deleted`.","example":"deleted"},"email":{"type":"string","title":"Email","description":"The email, lowercased.","example":"dana@example.com"}},"type":"object","required":["status","email"],"title":"DeprovisionUserResponse","description":"The app user whose access was removed."},"DirectoryEntry":{"properties":{"name":{"type":"string","title":"Name","description":"The entry's own name, without its parent path.","example":"Home.jsx"},"path":{"type":"string","title":"Path","description":"Full path relative to the app root.","example":"src/pages/Home.jsx"},"type":{"type":"string","enum":["file","directory"],"title":"Type","description":"Whether the entry is a file or a directory.","example":"file"},"size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Size","description":"Size in bytes. Present on files, absent on directories.","example":214}},"type":"object","required":["name","path","type"],"title":"DirectoryEntry","description":"One file or directory."},"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."},"DisconnectIntegrationResult":{"properties":{"status":{"type":"string","const":"disconnected","title":"Status","description":"Always `disconnected`.","example":"disconnected"},"integration_type":{"type":"string","title":"Integration Type","description":"Connector that was disconnected.","example":"googlecalendar"}},"type":"object","required":["status","integration_type"],"title":"DisconnectIntegrationResult","description":"Confirmation that the connector is disconnected from the app."},"DisconnectResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the app was disconnected. Always `true`, because a disconnect that fails returns an error instead.","example":true}},"type":"object","required":["success"],"title":"DisconnectResponse"},"DomainAvailabilityCheck":{"properties":{"query":{"type":"string","title":"Query","description":"The domain you asked about, normalized to lower case.","example":"nordwind-furniture.com"},"results":{"items":{"$ref":"#/components/schemas/DomainAvailabilityCheckResult"},"type":"array","title":"Results","description":"The domain you asked about, first, followed by up to five suggestions."}},"type":"object","required":["query","results"],"title":"DomainAvailabilityCheck","description":"Whether a domain can be registered, with similar domains that can."},"DomainAvailabilityCheckResult":{"properties":{"domain":{"type":"string","title":"Domain","description":"The domain this entry is about.","example":"nordwind-furniture.com"},"available":{"type":"boolean","title":"Available","description":"Whether Base44 can register the domain (`true`) or not (`false`).","example":true},"reason":{"anyOf":[{"type":"string","enum":["premium","unavailable"]},{"type":"null"}],"title":"Reason","description":"Why the domain can't be registered, or `null` when it can. Either `premium`, a premium-priced domain Base44 doesn't sell, or `unavailable` for every other reason.","example":"premium"}},"type":"object","required":["domain","available"],"title":"DomainAvailabilityCheckResult"},"DomainClaimChallenge":{"properties":{"detail":{"$ref":"#/components/schemas/DomainClaimChallengeDetail","description":"What to publish before adding the domain again."}},"type":"object","required":["detail"],"title":"DomainClaimChallenge","description":"Another workspace released this domain, so you have to prove you control it."},"DomainClaimChallengeDetail":{"properties":{"message":{"type":"string","title":"Message","description":"Why the domain can't be added yet.","example":"This domain is registered to another workspace. Add the TXT record below to prove you control it, then try again."},"verification":{"$ref":"#/components/schemas/DomainClaimVerification","description":"The record to publish at the domain's DNS provider before you retry."}},"type":"object","required":["message","verification"],"title":"DomainClaimChallengeDetail"},"DomainClaimVerification":{"properties":{"type":{"type":"string","title":"Type","description":"Record type. Always `TXT`.","example":"TXT"},"name":{"type":"string","title":"Name","description":"Host name to publish the record on.","example":"_b44-verify.example.com"},"value":{"type":"string","title":"Value","description":"Value to publish. It's issued to your workspace, so another workspace can't use it to claim the domain.","example":"base44-domain-claim=3kQ9vL2mP7xR8tN4wZ1yB6cF5hJ0dS2aE9gU7iO3rT"}},"type":"object","required":["type","name","value"],"title":"DomainClaimVerification","description":"The DNS record that proves you control the domain."},"DomainDnsRecord":{"properties":{"type":{"type":"string","enum":["A","CNAME"],"title":"Type","description":"Record type. Either `A` or `CNAME`.","example":"CNAME"},"name":{"type":"string","title":"Name","description":"Full name of the record, such as `www.example.com`. If your DNS provider asks for a host rather than a full name, enter the part before your root domain, or `@` for the root domain itself.","example":"www.example.com"},"value":{"type":"string","title":"Value","description":"What the record points to. Enter it exactly as returned.","example":"base44.onrender.com"},"ttl":{"type":"integer","title":"Ttl","description":"Time to live, in seconds.","example":300}},"type":"object","required":["type","name","value","ttl"],"title":"DomainDnsRecord"},"DomainDnsRecords":{"properties":{"records":{"items":{"$ref":"#/components/schemas/DomainDnsRecord"},"type":"array","title":"Records","description":"The records to create at the domain's DNS provider, on this page."},"pagination":{"$ref":"#/components/schemas/PublicPaginationInfo","description":"Where this page sits in the full list of records."}},"type":"object","required":["records","pagination"],"title":"DomainDnsRecords","description":"The DNS records that point a domain at the app."},"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."},"DomainPurchaseProvider":{"properties":{"provider":{"type":"string","enum":["wix","entri"],"title":"Provider","description":"Either `wix` or `entri`, the provider whose checkout the Base44 editor shows for a new domain.","example":"entri"}},"type":"object","required":["provider"],"title":"DomainPurchaseProvider","description":"Which provider sells this app's workspace a new domain."},"DomainPurchaseUrl":{"properties":{"url":{"type":"string","title":"Url","description":"Checkout link to open in the browser of the person who'll pay. Treat it as opaque. It works once, so create a new one for each checkout.","example":"https://checkout.example.com/session/7c1e9f2a4b8d4e6f9a3c5b7d1e2f4a6c"}},"type":"object","required":["url"],"title":"DomainPurchaseUrl","description":"A checkout link for buying a domain."},"DomainPurchaseUrlRequest":{"properties":{"domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain","description":"Domain to open checkout on, such as `example.com`. Leave it out to open checkout on a domain search.","example":"nordwind-furniture.com"},"offer_business_email":{"type":"boolean","title":"Offer Business Email","description":"Whether checkout also offers a business email address on the domain after the purchase (`true`) or not (`false`).","default":false,"example":false}},"type":"object","title":"DomainPurchaseUrlRequest"},"DomainRegistration":{"properties":{"domain":{"type":"string","title":"Domain","description":"The domain name.","example":"nordwind-furniture.com"},"can_manage":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Can Manage","description":"Whether the workspace's account holds the registration, so its renewal can be managed (`true`) or not (`false`). The value is `null` when Base44 couldn't read the registration.","example":true},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At","description":"When the current registration period ends, as an ISO 8601 timestamp. With `auto_renew` on, the domain renews then. The value is `null` when Base44 couldn't read it.","example":"2027-09-24T00:00:00.000Z"},"auto_renew":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Auto Renew","description":"Whether the registration renews automatically (`true`) or lapses at `expires_at` (`false`). The value is `null` when Base44 couldn't read it.","example":true}},"type":"object","required":["domain","can_manage","expires_at","auto_renew"],"title":"DomainRegistration"},"DomainRegistrations":{"properties":{"registrations":{"items":{"$ref":"#/components/schemas/DomainRegistration"},"type":"array","title":"Registrations","description":"One entry per domain bought through Base44 for this app, on this page."},"pagination":{"$ref":"#/components/schemas/PublicPaginationInfo","description":"Where this page sits in the full list of registrations."}},"type":"object","required":["registrations","pagination"],"title":"DomainRegistrations","description":"The registration status of the app's domains bought through Base44."},"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."},"EditAppCommentPayload":{"properties":{"content":{"type":"string","maxLength":5000,"minLength":1,"title":"Content","description":"Text of the comment, 1 to 5000 characters.","example":"Make this button match the footer color."},"mentioned_emails":{"items":{"type":"string"},"type":"array","maxItems":50,"title":"Mentioned Emails","description":"Emails of up to 50 people the comment mentions. Mentions send no notification.","example":["dana@acme.com"]}},"type":"object","required":["content"],"title":"EditAppCommentPayload","description":"The new text of a comment."},"EditFileResult":{"properties":{"path":{"type":"string","title":"Path","description":"The normalized path that was edited.","example":"src/pages/Home.jsx"},"diff":{"type":"string","title":"Diff","description":"Unified diff of the change, whether or not it was applied.","example":"--- src/pages/Home.jsx\n+++ src/pages/Home.jsx\n@@ -1,3 +1,3 @@\n-  return <h1>Hello</h1>;\n+  return <h1>Welcome</h1>;\n"},"applied":{"type":"boolean","title":"Applied","description":"`true` when the file was changed, `false` on a `dry_run` preview.","example":true},"warnings":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Warnings","description":"Advisories about the write that did not stop it. Absent entirely on a `dry_run`, since nothing was written.","example":[]}},"type":"object","required":["path","diff","applied"],"title":"EditFileResult","description":"The diff, and whether it landed."},"EditQueueItemPayload":{"properties":{"content":{"type":"string","title":"Content","description":"New text for the queued message. Up to 100,000 characters.","example":"Add a contact form with a phone number field to the home page"}},"type":"object","required":["content"],"title":"EditQueueItemPayload"},"EffectiveEmbeddingPolicyResult":{"properties":{"mode":{"type":"string","enum":["anyone","block_all","allowlist"],"title":"Mode","description":"Who can show the published app in a frame. `anyone` means any site can, `block_all` means no site can, and `allowlist` means only `origins` can.","example":"allowlist"},"source":{"type":"string","enum":["default","app","workspace"],"title":"Source","description":"Where the policy comes from. `app` is the app's own setting, `workspace` is its workspace's policy, and `default` means neither sets one.","example":"app"},"origins":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Origins","description":"Sites allowed to frame the app when `mode` is `allowlist`, or `null` for the other modes.","example":["https://partners.acme.com"]}},"type":"object","required":["mode","source","origins"],"title":"EffectiveEmbeddingPolicyResult","description":"A published app's framing policy after its workspace policy is applied."},"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."},"EmailStatDayDoc":{"properties":{"sent":{"type":"integer","title":"Sent","description":"Emails sent.","example":1240},"delivered":{"type":"integer","title":"Delivered","description":"Emails the recipient's mail server accepted.","example":1198},"opened":{"type":"integer","title":"Opened","description":"Opens, counting each open, so one recipient can count more than once.","example":512},"clicked":{"type":"integer","title":"Clicked","description":"Link clicks, counting each click, so one recipient can count more than once.","example":143},"bounced":{"type":"integer","title":"Bounced","description":"Emails that bounced.","example":31},"spam":{"type":"integer","title":"Spam","description":"Emails recipients marked as spam.","example":2},"unsubscribed":{"type":"integer","title":"Unsubscribed","description":"Recipients who unsubscribed through the email.","example":4},"day":{"type":"string","format":"date","title":"Day","description":"The UTC day the counts are for.","example":"2026-09-14"}},"type":"object","required":["sent","delivered","opened","clicked","bounced","spam","unsubscribed","day"],"title":"EmailStatDayDoc"},"EmailStatTotalsDoc":{"properties":{"sent":{"type":"integer","title":"Sent","description":"Emails sent.","example":1240},"delivered":{"type":"integer","title":"Delivered","description":"Emails the recipient's mail server accepted.","example":1198},"opened":{"type":"integer","title":"Opened","description":"Opens, counting each open, so one recipient can count more than once.","example":512},"clicked":{"type":"integer","title":"Clicked","description":"Link clicks, counting each click, so one recipient can count more than once.","example":143},"bounced":{"type":"integer","title":"Bounced","description":"Emails that bounced.","example":31},"spam":{"type":"integer","title":"Spam","description":"Emails recipients marked as spam.","example":2},"unsubscribed":{"type":"integer","title":"Unsubscribed","description":"Recipients who unsubscribed through the email.","example":4},"delivered_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Delivered Rate","description":"`delivered` divided by `sent`, from 0 to 1, or `null` when nothing was sent.","example":0.966},"bounce_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Bounce Rate","description":"`bounced` divided by `sent`, from 0 to 1, or `null` when nothing was sent.","example":0.025}},"type":"object","required":["sent","delivered","opened","clicked","bounced","spam","unsubscribed","delivered_rate","bounce_rate"],"title":"EmailStatTotalsDoc"},"EmailStats":{"properties":{"days":{"type":"integer","title":"Days","description":"Number of days counted, as requested.","example":30},"templates":{"items":{"$ref":"#/components/schemas/TemplateEmailStatsDoc"},"type":"array","title":"Templates","description":"Counts for each template that sent email in either period, ordered by `template_key`.","example":[{"daily":[{"bounced":1,"clicked":5,"day":"2026-09-14","delivered":41,"opened":18,"sent":42,"spam":0,"unsubscribed":0}],"template_key":"Welcome","totals":{"bounce_rate":0.025,"bounced":31,"clicked":143,"delivered":1198,"delivered_rate":0.966,"opened":512,"sent":1240,"spam":2,"unsubscribed":4},"tracked":true}]},"totals":{"$ref":"#/components/schemas/EmailStatTotalsDoc","description":"Totals for the period across every template and emails sent without one, on every page."},"previous_totals":{"anyOf":[{"$ref":"#/components/schemas/EmailStatTotalsDoc"},{"type":"null"}],"description":"Totals for the same number of days just before the period, or `null` when nothing was sent then."},"daily":{"items":{"$ref":"#/components/schemas/EmailStatDayDoc"},"type":"array","title":"Daily","description":"One entry per day of the period, oldest first, with zeros on days with no activity. Covers every template, on every page.","example":[{"bounced":1,"clicked":5,"day":"2026-09-14","delivered":41,"opened":18,"sent":42,"spam":0,"unsubscribed":0}]},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass it as `cursor` to get the next page of `templates`, or `null` on the last page.","example":"eyJuIjoiYXBwX2VtYWlsX3N0YXRzIiwicCI6eyJhZnRlciI6IldlbGNvbWUifX0"}},"type":"object","required":["days","templates","totals","previous_totals","daily","next_cursor"],"title":"EmailStats","description":"Delivery and engagement counts for the app's sent emails."},"EmailTemplateDetail":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the template.","example":"68c1f0a2b3d4e5f6a7b8c9d0"},"app_id":{"type":"string","title":"App Id","description":"ID of the app the template belongs to.","example":"6820f3a4e7b91d003c45a1f2"},"name":{"type":"string","title":"Name","description":"Name of the template, from its file name: the template in `base44/emails/Welcome.html` is `Welcome`. A workflow's send email action or a backend function's `SendEmail` call picks the template by this name.","example":"Welcome"},"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"When the template was created, as a UTC timestamp in ISO 8601 format.","example":"2026-09-14T08:30:12.418000Z"},"updated_date":{"type":"string","format":"date-time","title":"Updated Date","description":"When the template last changed, as a UTC timestamp in ISO 8601 format.","example":"2026-09-14T08:30:12.418000Z"},"html":{"type":"string","title":"Html","description":"The email as inbox-ready HTML. Its `<title>` is the default subject line. `{{variable}}` placeholders, such as `{{first_name}}`, stay as written. The sender fills them in.","example":"<!doctype html><html><head><title>Welcome to Acme</title></head><body>Hi {{first_name}}</body></html>"}},"type":"object","required":["id","app_id","name","created_date","updated_date","html"],"title":"EmailTemplateDetail","description":"An email template with its HTML."},"EmailTemplateSummary":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the template.","example":"68c1f0a2b3d4e5f6a7b8c9d0"},"app_id":{"type":"string","title":"App Id","description":"ID of the app the template belongs to.","example":"6820f3a4e7b91d003c45a1f2"},"name":{"type":"string","title":"Name","description":"Name of the template, from its file name: the template in `base44/emails/Welcome.html` is `Welcome`. A workflow's send email action or a backend function's `SendEmail` call picks the template by this name.","example":"Welcome"},"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"When the template was created, as a UTC timestamp in ISO 8601 format.","example":"2026-09-14T08:30:12.418000Z"},"updated_date":{"type":"string","format":"date-time","title":"Updated Date","description":"When the template last changed, as a UTC timestamp in ISO 8601 format.","example":"2026-09-14T08:30:12.418000Z"}},"type":"object","required":["id","app_id","name","created_date","updated_date"],"title":"EmailTemplateSummary","description":"An email template, without its HTML."},"EmailTemplates":{"items":{"$ref":"#/components/schemas/EmailTemplateSummary"},"type":"array","title":"EmailTemplates","description":"The app's email templates."},"EmbedTarget":{"type":"string","enum":["live_site","latest_preview","live_preview"],"title":"EmbedTarget","description":"The surfaces a platform may frame, named by their ``UrlType``.\n\nA subset rather than ``UrlType`` itself: the rest of that enum is either\ninternal (``platform``, ``pentest``) or addresses a revision, and a public\npayload should not offer what it will only refuse."},"EmbedTokenPayload":{"properties":{"email":{"type":"string","format":"email","title":"Email","description":"The app user's email"},"target":{"$ref":"#/components/schemas/EmbedTarget","description":"Which of the app's surfaces to frame: live_site (the default, the published app), latest_preview (main's static build) or live_preview (the running sandbox).","default":"live_site"}},"additionalProperties":false,"type":"object","required":["email"],"title":"EmbedTokenPayload","description":"The app user to mint for, and which of the app's surfaces to frame."},"EmbedTokenResponse":{"properties":{"token":{"type":"string","title":"Token","description":"The single-use sign-in token. Already on `embed_url`; you don't need to send it anywhere yourself.","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.sig"},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds until the token expires.","example":60},"embed_url":{"type":"string","title":"Embed Url","description":"The chosen surface's address with the token on it. Load it in an iframe.","example":"https://weekly-report.base44.app/?ott=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.sig"}},"type":"object","required":["token","expires_in","embed_url"],"title":"EmbedTokenResponse","description":"A single-use sign-in token, and the URL that spends it."},"EmbeddedPaymentSetupSession":{"properties":{"client_secret":{"type":"string","title":"Client Secret","description":"Token that mounts Stripe's embedded checkout in a browser. It is meant for client-side use and is scoped to this one session, so it is not a secret key, but it does let whoever holds it complete this card setup.","example":"cs_live_a1B2c3D4e5_secret_f6G7h8I9j0"},"session_id":{"type":"string","title":"Session Id","description":"Stripe's ID for the session.","example":"cs_live_a1B2c3D4e5"},"publishable_key":{"type":"string","title":"Publishable Key","description":"Base44's Stripe publishable key, which the embedded checkout needs. It is public by design.","example":"pk_live_51H8xY2eZvKYlo2C"}},"type":"object","required":["client_secret","session_id","publishable_key"],"title":"EmbeddedPaymentSetupSession","description":"A Stripe Checkout session for saving a card inside your own page."},"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."},"FailedInvitation":{"properties":{"email":{"type":"string","title":"Email","description":"Email the invitation was for.","example":"sam@acme.com"},"error":{"type":"string","title":"Error","description":"Why it wasn't sent. Its wording can change, so don't parse it.","example":"User already has a pending invitation"}},"type":"object","required":["email","error"],"title":"FailedInvitation","description":"An invitation that wasn't sent."},"FileContent":{"properties":{"path":{"type":"string","title":"Path","description":"Path of the file, relative to the app root.","example":"src/pages/Home.jsx"},"content":{"type":"string","title":"Content","description":"The file's full text.","example":"export default function Home() {\n  return <h1>Home</h1>;\n}\n"}},"type":"object","required":["path","content"],"title":"FileContent","description":"One file's text."},"FileList":{"properties":{"paths":{"items":{"type":"string"},"type":"array","title":"Paths","description":"Path of each file, relative to the app root.","example":["package.json","src/App.jsx","src/pages/Home.jsx"]}},"type":"object","required":["paths"],"title":"FileList","description":"The paths of the app's source files."},"FileResult":{"properties":{"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content","description":"The file's full text. Left out when `error` is set.","example":"export default function Home() {\n  return <h1>Home</h1>;\n}\n"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why the file couldn't be read: `file_not_found`, `file_too_large`, `batch_limit_exceeded`, `not_text`, `file_forbidden`, `invalid_path` or `file_read_failed`. Left out when the file was read.","example":"file_not_found"}},"type":"object","title":"FileResult","description":"One file's text, or why it couldn't be read."},"FlowResponse":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the test.","example":"68a1c2e4f0b3d9001a7e5c21"},"app_id":{"type":"string","title":"App Id","description":"ID of the app the test belongs to.","example":"6820f3a4e7b91d003c45a1f2"},"name":{"type":"string","title":"Name","description":"Short name of the test.","example":"Create a project"},"goal":{"type":"string","title":"Goal","description":"What the testing agent tries to do, in plain language.","example":"Sign up, create a project called Launch, and check that it appears on the dashboard."},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"Role set on the test, or `null` when none is set. A test without one can still run as a role its name or goal names. The run's `flow_role` shows the role actually used.","example":"admin"},"source":{"type":"string","title":"Source","description":"`manual` for a test you created, `app_context` for one Base44 generated from the app.","example":"manual"},"revision":{"type":"integer","title":"Revision","description":"Starts at 1 and goes up each time the test is edited.","example":1},"created_date":{"type":"string","title":"Created Date","description":"When the test was created, as an ISO 8601 UTC timestamp.","example":"2026-09-28T10:15:00+00:00"},"updated_date":{"type":"string","title":"Updated Date","description":"When the test last changed, as an ISO 8601 UTC timestamp.","example":"2026-09-28T10:15:00+00:00"}},"type":"object","required":["id","app_id","name","goal","source","revision","created_date","updated_date"],"title":"FlowResponse","description":"A test: a goal the testing agent tries to accomplish in a browser."},"FunctionLogLine":{"properties":{"time":{"type":"string","title":"Time","description":"When the line was logged, as a UTC timestamp in ISO 8601 format.","example":"2026-01-15T09:23:41.482000+00:00"},"level":{"type":"string","title":"Level","description":"Severity of the line, such as `info`, `warn`, or `error`.","example":"info"},"message":{"type":"string","title":"Message","description":"What the function wrote. An uncaught error carries its name, message and stack trace. Each invocation also gets a line of its own with the request method and response status, like `POST → 200`.","example":"Sent welcome email to jane@acme.com"}},"type":"object","required":["time","level","message"],"title":"FunctionLogLine","description":"One line a backend function logged."},"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":{"scheduled_post_id":{"anyOf":[{"type":"string","pattern":"^[0-9a-fA-F]{24}$"},{"type":"null"}],"title":"Scheduled Post Id","description":"Optional calendar post ID from List scheduled posts. Its source_post_id must match the post_id in the URL. Updates the calendar row rather than the original plan; requires Social Calendar V2. Only proposal and scheduled posts can be edited.","example":"6820f3a4e7b91d003c45a1f2"},"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."},"GeneratedTests":{"properties":{"flows":{"items":{"$ref":"#/components/schemas/FlowResponse"},"type":"array","title":"Flows","description":"The saved tests, up to 5. Empty when `no_changes` is `true`.","example":[{"app_id":"6820f3a4e7b91d003c45a1f2","created_date":"2026-09-28T10:15:00+00:00","goal":"Sign up, create a project called Launch, and check that it appears on the dashboard.","id":"68a1c2e4f0b3d9001a7e5c21","name":"Create a project","revision":1,"source":"app_context","updated_date":"2026-09-28T10:15:00+00:00"}]},"no_changes":{"type":"boolean","title":"No Changes","description":"`true` when the app has no pages or entities yet, so nothing was generated or charged.","example":false}},"type":"object","required":["flows","no_changes"],"title":"GeneratedTests","description":"Tests Base44 generated and saved."},"GitHubConnectionMode":{"type":"string","enum":["shared","per_user"],"title":"GitHubConnectionMode"},"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"},"missing_pr_automation_permissions":{"items":{"type":"string"},"type":"array","title":"Missing Pr Automation Permissions","description":"GitHub App permissions required for PR automation but not granted."},"missing_pr_automation_events":{"items":{"type":"string"},"type":"array","title":"Missing Pr Automation Events","description":"GitHub App webhook subscriptions required for PR automation but absent."}},"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. See [Pull error codes](/developers/references/apps-api/sections/github#pull-error-codes) for the possible values.","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."},"GitHubReconnectResult":{"properties":{"direction":{"type":"string","enum":["none","base44_to_github","github_to_base44"],"title":"Direction","description":"Which side Base44 brought up to date. The value is one of:\n- `none`: The two already matched.\n- `base44_to_github`: Base44 pushed the app's newer commits.\n- `github_to_base44`: Base44 pulled the repository's newer commits.","example":"base44_to_github"},"repo_full_name":{"type":"string","title":"Repo Full Name","description":"Full repository name, as `owner/repo`.","example":"base44/lead-tracker"},"repo_url":{"type":"string","title":"Repo Url","description":"URL of the repository on GitHub.","example":"https://github.com/base44/lead-tracker"},"base44_head":{"type":"string","title":"Base44 Head","description":"The app's latest commit when the reconnect started.","example":"4c7e1f90ab3d5628e1a0f7b24c9d8e6350a1b2c4"},"github_head":{"type":"string","title":"Github Head","description":"Head of the repository's default branch when the reconnect started.","example":"9f2c1ab7d4e58036bb1f4c0a7de92b41c0d5e8a3"},"sync":{"anyOf":[{"$ref":"#/components/schemas/GitHubPullResult"},{"type":"null"}],"description":"Outcome of pulling the repository's newer commits into the app, or `null` unless `direction` is `github_to_base44`."}},"type":"object","required":["direction","repo_full_name","repo_url","base44_head","github_head"],"title":"GitHubReconnectResult"},"GitHubWorkspaceInstallation":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the installation in Base44. Pass it as `workspace_installation_id` to [Connect a GitHub repository](/api-reference/connect-a-github-repository).","example":"66f1c2d9a4b7e80012ab34cd"},"github_installation_id":{"type":"string","title":"Github Installation Id","description":"ID of the Base44 GitHub App installation on GitHub.","example":"58231904"},"account_login":{"type":"string","title":"Account Login","description":"GitHub organization the installation is on.","example":"acme-inc"},"account_avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Avatar Url","description":"URL of the organization's GitHub avatar, or `null` when GitHub didn't return one.","example":"https://avatars.githubusercontent.com/u/1234567?v=4"},"repository_selection":{"type":"string","enum":["all","selected"],"title":"Repository Selection","description":"Whether the Base44 GitHub App can reach all of the organization's repositories (`all`) or only chosen ones (`selected`). You can't create a repository through an installation set to `selected`, so switch it to all repositories in GitHub first.","example":"all"}},"type":"object","required":["id","github_installation_id","account_login","repository_selection"],"title":"GitHubWorkspaceInstallation"},"GitHubWorkspaceInstallations":{"items":{"$ref":"#/components/schemas/GitHubWorkspaceInstallation"},"type":"array","title":"GitHubWorkspaceInstallations","description":"The workspace's GitHub installations you can use."},"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."},"GoogleAdsAccountDebtSummary":{"properties":{"state":{"type":"string","title":"State","description":"What can be done about the balance. `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` means the account is held with nothing to settle, and `not_recoverable` means the account is not in a state that settling can fix, which includes an account that is not held at all.","example":"payable"},"account_id":{"type":"string","title":"Account Id","description":"ID of the Google Ads account, the one in the path.","example":"68b1c0d4e7b91d003c45a1f8"},"account_status":{"type":"string","title":"Account Status","description":"Status of the Google Ads account.","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":{"type":"string","title":"Charge Trigger Mode","description":"How the account is billed. `cadence` is the recurring charge and `threshold` is spend-triggered.","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","account_id","account_status","charge_trigger_mode","recoverable","can_pay_now","requires_payment_method","stripe_unavailable"],"title":"GoogleAdsAccountDebtSummary","description":"Whether one Google Ads account owes anything, and whether it can be settled."},"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: `3d`, `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"},"customer_grade":{"type":"integer","title":"Customer Grade","description":"How far the workspace has earned higher limits by paying on time. `0` until 3 invoices in a row are paid with no decline, then `1`, rising by one per further clean invoice. It never goes down; it reads `0` only while an account in the workspace is on hold or blocked.","default":0,"example":1},"active_campaign_limit":{"type":"integer","title":"Active Campaign Limit","description":"How many of the account's campaigns can be enabled at once. `2`, plus one per `customer_grade`, up to `5`.","default":2,"example":3},"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."},"GoogleAdsAdvertisingLanguages":{"properties":{"languages":{"items":{"type":"string"},"type":"array","title":"Languages","description":"Two-letter ISO 639-1 codes, sorted alphabetically. Name them in your own interface language yourself; the API returns codes only.","example":["ar","de","en","es","fr","he"]}},"type":"object","required":["languages"],"title":"GoogleAdsAdvertisingLanguages","description":"The languages a campaign's ads can run in."},"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},"clicks_per_unit_min":{"type":"number","title":"Clicks Per Unit Min","description":"Low end of the clicks one unit of `currency_code` in daily budget has actually bought. Multiply a daily budget by this directly — it is already in the same currency the budget is, so no conversion is involved. Always present — check `clicks_per_unit_scope` to see whether it is a live measurement.","example":1.9},"clicks_per_unit_max":{"type":"number","title":"Clicks Per Unit Max","description":"High end of the same range, with the same caveat: measured when `clicks_per_unit_scope` is `campaign` or `fleet`, a stored rate when it is `unavailable`.","example":21.2},"clicks_per_unit_scope":{"type":"string","title":"Clicks Per Unit Scope","description":"Where the click range came from, and whether it is a measurement at all. `campaign`: measured on the campaign named by `campaign_id`. `fleet`: measured across accounts billing in this currency — also what an unknown campaign, or one without enough delivery, returns. `unavailable`: NOT measured — a stored range is being served because this currency has too few accounts to measure or the measurement could not be read. The range is usable in every case, but only the first two are measured.","example":"fleet"}},"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","clicks_per_unit_min","clicks_per_unit_max","clicks_per_unit_scope"],"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 in a set of locations."},"GoogleAdsBudgetOption":{"properties":{"dailyAmountMicros":{"type":"string","title":"Dailyamountmicros","description":"The suggested daily budget in micros of the account currency, as a string, so `\"30000000\"` is 30.00.","default":"0","example":"30000000"},"metrics":{"anyOf":[{"$ref":"#/components/schemas/GoogleAdsBudgetOptionMetrics"},{"type":"null"}],"description":"Google's click forecast at this budget. Absent or `null` when Google has none."}},"type":"object","title":"GoogleAdsBudgetOption","description":"One daily budget Google suggests, with its forecast."},"GoogleAdsBudgetOptionMetrics":{"properties":{"minDailyClicks":{"type":"string","title":"Mindailyclicks","description":"Low end of the forecast daily clicks. Google sends 64-bit integers as strings, and leaves the field out when it is `0`.","default":"0","example":"12"},"maxDailyClicks":{"type":"string","title":"Maxdailyclicks","description":"High end of the forecast daily clicks, as a string. Left out when it is `0`.","default":"0","example":"31"}},"type":"object","title":"GoogleAdsBudgetOptionMetrics","description":"Google's daily click forecast for one budget option."},"GoogleAdsBudgetOptions":{"properties":{"low":{"anyOf":[{"$ref":"#/components/schemas/GoogleAdsBudgetOption"},{"type":"null"}],"description":"The lowest budget Google suggests. Absent or `null` when Google has no tier at this level."},"recommended":{"anyOf":[{"$ref":"#/components/schemas/GoogleAdsBudgetOption"},{"type":"null"}],"description":"The budget Google recommends. Absent or `null` when Google has none."},"high":{"anyOf":[{"$ref":"#/components/schemas/GoogleAdsBudgetOption"},{"type":"null"}],"description":"The highest budget Google suggests. Absent or `null` when Google has no tier at this level."}},"type":"object","title":"GoogleAdsBudgetOptions","description":"Google's low, recommended and high daily budgets for a Smart campaign."},"GoogleAdsCampaignImpact":{"properties":{"goals":{"additionalProperties":{"$ref":"#/components/schemas/GoogleAdsImpactGoalTotals"},"type":"object","title":"Goals","description":"Totals per goal family, keyed by `sales`, `leads`, `shopping` and `other`. Empty when `goals_unavailable` is `true`.","example":{"leads":{"conversions":6.0,"conversions_value":0.0},"other":{"conversions":0.0,"conversions_value":0.0},"sales":{"conversions":24.0,"conversions_value":1830.0},"shopping":{"conversions":9.0,"conversions_value":410.0}}},"goal_actions":{"items":{"$ref":"#/components/schemas/GoogleAdsImpactGoalAction"},"type":"array","title":"Goal Actions","description":"The conversion categories behind `goals`, most conversions first. Categories that round to zero conversions are left out here but still count in `goals`. Absent when `goals_unavailable` is `true`.","example":[{"category":"PURCHASE","conversions":12.0,"conversions_value":960.0,"family":"sales"}]},"goals_unavailable":{"type":"boolean","title":"Goals Unavailable","description":"`true` when the goal read failed, so `goals` is empty because the numbers are missing, not zero.","example":false},"channels":{"items":{"$ref":"#/components/schemas/GoogleAdsImpactChannel"},"type":"array","title":"Channels","description":"Conversions per ad network, most first. Empty when `channels_unavailable` is `true`.","example":[{"channel":"SEARCH","conversions":18.0}]},"channels_unresolved":{"type":"number","title":"Channels Unresolved","description":"Conversions Google reported with no resolved network, which includes every Performance Max conversion before 1 June 2025. Absent when `channels_unavailable` is `true`.","default":0.0,"example":2.0},"channels_unavailable":{"type":"boolean","title":"Channels Unavailable","description":"`true` when the channel read failed, so `channels` is empty because the numbers are missing, not zero.","example":false},"countries":{"items":{"$ref":"#/components/schemas/GoogleAdsImpactCountry"},"type":"array","title":"Countries","description":"Conversions per country the people who converted were in, most first. Empty when `locations_unavailable` is `true`.","example":[{"code":"DE","conversions":20.0}]},"locations":{"items":{"$ref":"#/components/schemas/GoogleAdsImpactLocation"},"type":"array","title":"Locations","description":"Conversions per city, or the most specific place Google resolved, most first. Empty when `locations_unavailable` is `true`.","example":[{"conversions":11.0,"name":"Berlin"}]},"countries_unresolved":{"type":"number","title":"Countries Unresolved","description":"Conversions Base44 could not name a country for, so they are missing from `countries`. Absent when `locations_unavailable` is `true`.","default":0.0,"example":0.0},"locations_unresolved":{"type":"number","title":"Locations Unresolved","description":"Conversions Base44 could not name a place for, so they are missing from `locations`. Absent when `locations_unavailable` is `true`.","default":0.0,"example":1.0},"locations_unavailable":{"type":"boolean","title":"Locations Unavailable","description":"`true` when the location read failed, so `countries` and `locations` are empty because the numbers are missing, not zero.","example":false},"goals_configured":{"items":{"type":"string"},"type":"array","title":"Goals Configured","description":"The goal families, `sales` and `leads`, that the app has an enabled conversion mapping for. Empty when Base44 can't tell, in which case treat both as possible.","example":["sales"]}},"type":"object","required":["goals","goals_unavailable","channels","channels_unavailable","countries","locations","locations_unavailable","goals_configured"],"title":"GoogleAdsCampaignImpact","description":"One campaign's conversions, broken down by goal, channel and location."},"GoogleAdsCampaignLaunchDraft":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the draft.","example":"68c2d1e5f8a92e004d56b2a3"},"app_id":{"type":"string","title":"App Id","description":"ID of the app the draft is for.","example":"6820f3a4e7b91d003c45a1f2"},"campaign_name":{"type":"string","title":"Campaign Name","description":"Name for the campaign. Empty when the chat did not choose one.","example":"Spring sale in Berlin"},"business_name":{"type":"string","title":"Business Name","description":"Business name to show in the ads. Empty when the chat did not set one.","example":"Oakline Furniture"},"landing_page_url":{"type":"string","title":"Landing Page Url","description":"Page the ads send clicks to. Empty when the chat did not set one.","example":"https://oakline.base44.app"},"phone_number":{"type":"string","title":"Phone Number","description":"Phone number for a call button, in a form Google can place. Empty when there is none, including when the chat suggested a number Google couldn't place.","example":"+493012345678"},"daily_budget_micros":{"type":"integer","title":"Daily Budget Micros","description":"Daily budget in micros of `currency_code`, so `15000000` is 15.00.","example":15000000},"currency_code":{"type":"string","title":"Currency Code","description":"Currency of `daily_budget_micros`, the currency the account is charged in, as a three-letter ISO 4217 code. It can differ from the currency of the Google Ads account's own budget.","example":"EUR"},"headlines":{"items":{"type":"string"},"type":"array","title":"Headlines","description":"Headlines, up to 15.","example":["Handmade oak tables","Built in Berlin"]},"long_headlines":{"items":{"type":"string"},"type":"array","title":"Long Headlines","description":"Long headlines, up to 5.","example":["Solid oak dining tables, made to order in Berlin"]},"descriptions":{"items":{"type":"string"},"type":"array","title":"Descriptions","description":"Descriptions, up to 5.","example":["Visit our showroom or order online with free delivery."]},"images":{"items":{"$ref":"#/components/schemas/GoogleAdsLaunchDraftImage"},"type":"array","title":"Images","description":"Pictures for the campaign, up to 20.","example":[{"field_type":"MARKETING_IMAGE","url":"https://storage.base44.com/gads-assets/b7f3a1c8-landscape.png"}]},"logos":{"items":{"$ref":"#/components/schemas/GoogleAdsLaunchDraftImage"},"type":"array","title":"Logos","description":"Logos, up to 5. Often empty, and a Performance Max campaign needs one to launch.","example":[{"field_type":"MARKETING_IMAGE","url":"https://storage.base44.com/gads-assets/logo.png"}]},"geo_targets":{"items":{"type":"string"},"type":"array","title":"Geo Targets","description":"Locations to target, as Google geo target constants. Empty means everywhere.","example":["geoTargetConstants/1003854"]},"targeted_locations":{"items":{"$ref":"#/components/schemas/GoogleAdsLaunchDraftLocation"},"type":"array","title":"Targeted Locations","description":"The same locations as `geo_targets`, with their names.","example":[{"name":"Berlin","resource_name":"geoTargetConstants/1003854"}]},"language":{"type":"string","title":"Language","description":"Two-letter code of the language the ads are written in.","example":"de"},"launched_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Launched At","description":"When someone launched a campaign from this draft's review page in the builder, or `null` while nobody has. Launching through the API doesn't set it.","example":"2026-09-14T10:20:00Z"},"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"When the chat saved the draft.","example":"2026-09-14T10:02:00Z"}},"type":"object","required":["id","app_id","campaign_name","business_name","landing_page_url","phone_number","daily_budget_micros","currency_code","headlines","long_headlines","descriptions","images","logos","geo_targets","targeted_locations","language","created_date"],"title":"GoogleAdsCampaignLaunchDraft","description":"A Performance Max campaign the builder chat prepared for review."},"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, as image objects in the shape [Create campaign](/api-reference/create-google-ads-campaign) documents for `images`. Create rejects a bare URL string.","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."},"GoogleAdsCpcEstimate":{"properties":{"cpc_low_micros":{"type":"integer","title":"Cpc Low Micros","description":"Low end of the estimated cost of one click, in micros of `currency_code`, so `850000` is 0.85. `0` when there is no estimate.","example":850000},"cpc_high_micros":{"type":"integer","title":"Cpc High Micros","description":"High end of the estimated cost of one click, in micros of `currency_code`. `0` when there is no estimate.","example":2400000},"currency_code":{"type":"string","title":"Currency Code","description":"Currency of both figures as a three-letter ISO 4217 code. It is the account's Google Ads budget currency, the same one campaign budgets are set in.","example":"USD"},"sample_size":{"type":"integer","title":"Sample Size","description":"How many of Google's keyword ideas the range is averaged over, at most 10. `0` means Google returned none it could price, so there is no estimate.","example":10}},"type":"object","required":["cpc_low_micros","cpc_high_micros","currency_code","sample_size"],"title":"GoogleAdsCpcEstimate","description":"What a click is likely to cost for a landing page in a set of locations."},"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."},"GoogleAdsImpactChannel":{"properties":{"channel":{"type":"string","title":"Channel","description":"Google's ad network, passed through as Google reports it, for example `SEARCH`, `YOUTUBE` or `CONTENT`.","example":"SEARCH"},"conversions":{"type":"number","title":"Conversions","description":"Conversions through this network, rounded to two decimals.","example":18.0}},"type":"object","required":["channel","conversions"],"title":"GoogleAdsImpactChannel","description":"Conversions that came through one Google surface."},"GoogleAdsImpactCountry":{"properties":{"code":{"type":"string","title":"Code","description":"ISO 3166-1 alpha-2 code of the country.","example":"DE"},"conversions":{"type":"number","title":"Conversions","description":"Conversions from people in this country, rounded to two decimals.","example":20.0}},"type":"object","required":["code","conversions"],"title":"GoogleAdsImpactCountry","description":"Conversions from people in one country."},"GoogleAdsImpactGoalAction":{"properties":{"category":{"type":"string","title":"Category","description":"Google's conversion category, passed through as Google reports it, for example `PURCHASE`, `SUBMIT_LEAD_FORM` or `BEGIN_CHECKOUT`.","example":"PURCHASE"},"family":{"type":"string","title":"Family","description":"The goal family the category counts toward in `goals`: `sales`, `leads`, `shopping` or `other`.","example":"sales"},"conversions":{"type":"number","title":"Conversions","description":"Conversions in this category, rounded to two decimals. Can be negative when Google retracted conversions.","example":12.0},"conversions_value":{"type":"number","title":"Conversions Value","description":"Total value of those conversions in the account's currency, rounded to two decimals.","example":960.0}},"type":"object","required":["category","family","conversions","conversions_value"],"title":"GoogleAdsImpactGoalAction","description":"One conversion category's share of the campaign's conversions."},"GoogleAdsImpactGoalTotals":{"properties":{"conversions":{"type":"number","title":"Conversions","description":"Conversions in the window, rounded to two decimals.","example":24.0},"conversions_value":{"type":"number","title":"Conversions Value","description":"Total value of those conversions in the account's currency, rounded to two decimals.","example":1830.0}},"type":"object","required":["conversions","conversions_value"],"title":"GoogleAdsImpactGoalTotals","description":"Conversions and their value for one goal family."},"GoogleAdsImpactLocation":{"properties":{"name":{"type":"string","title":"Name","description":"Name of the city, or of the most specific place Google resolved when it has no city. Names that would be ambiguous carry their region or country.","example":"Berlin"},"conversions":{"type":"number","title":"Conversions","description":"Conversions from people in this place, rounded to two decimals.","example":11.0}},"type":"object","required":["name","conversions"],"title":"GoogleAdsImpactLocation","description":"Conversions from people in one place."},"GoogleAdsIncentiveOffer":{"properties":{"id":{"type":"string","title":"Id","description":"Google's ID for the offer.","example":"1234567"},"code":{"type":"string","title":"Code","description":"Token to send as `coupon_code` to [Apply a Google Ads incentive](/api-reference/apply-a-google-ads-incentive). Treat it as opaque. It's bound to the currency the offer was quoted in.","example":"SELECTED:1234567:USD"},"currency_code":{"type":"string","title":"Currency Code","description":"Currency of `reward_amount_micros` and `required_min_spend_micros`, as an ISO 4217 code. It's the account's currency, or the currency a new account would be created in when you left out `account_id`.","example":"USD"},"approximate":{"type":"boolean","title":"Approximate","description":"Whether the amounts were converted from the currency Google quoted the offer in (`true`) or are Google's own figures (`false`). Google converts again at its own rate when it grants the reward, so a converted amount can differ slightly from the credit granted.","example":false},"reward_amount_micros":{"type":"integer","title":"Reward Amount Micros","description":"Ad credit Google grants once the spend requirement is met, in micros of `currency_code`, so `500000000` is 500.00.","example":500000000},"required_min_spend_micros":{"type":"integer","title":"Required Min Spend Micros","description":"Ad spend the account has to reach to earn the reward, in micros of `currency_code`.","example":500000000},"terms_url":{"type":"string","title":"Terms Url","description":"Google's terms and conditions for the offer. Empty when Google didn't return a link.","example":"https://ads.google.com/intl/en/home/terms-and-conditions/incentives/"}},"type":"object","required":["id","code","currency_code","approximate","reward_amount_micros","required_min_spend_micros","terms_url"],"title":"GoogleAdsIncentiveOffer","description":"One promotional offer the app's Google Ads account can take."},"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."},"GoogleAdsLaunchDraftImage":{"properties":{"url":{"type":"string","title":"Url","description":"Address of the picture.","example":"https://storage.base44.com/gads-assets/b7f3a1c8-landscape.png"},"field_type":{"type":"string","title":"Field Type","description":"Which slot the picture fills on a Performance Max campaign, for example `MARKETING_IMAGE` or `SQUARE_MARKETING_IMAGE`. Ignore it on `logos` entries.","example":"MARKETING_IMAGE"}},"type":"object","required":["url","field_type"],"title":"GoogleAdsLaunchDraftImage","description":"One picture saved on a launch draft."},"GoogleAdsLaunchDraftLocation":{"properties":{"resource_name":{"type":"string","title":"Resource Name","description":"Google's identifier for the location, as it appears in `geo_targets`.","example":"geoTargetConstants/1003854"},"name":{"type":"string","title":"Name","description":"The location's name.","example":"Berlin"}},"type":"object","required":["resource_name","name"],"title":"GoogleAdsLaunchDraftLocation","description":"One location saved on a launch draft, with its name."},"GoogleAdsRegenerateKeywordRequest":{"properties":{"current":{"type":"string","title":"Current","description":"The theme to replace. Only its first 80 characters are used.","default":"","example":"phone repair"},"existing":{"items":{"type":"string"},"type":"array","title":"Existing","description":"Every theme already in the campaign, so the replacement is new. Only the first 25 are used, each cut to 80 characters.","example":["phone repair","screen replacement","battery replacement"]}},"additionalProperties":false,"type":"object","title":"GoogleAdsRegenerateKeywordRequest"},"GoogleAdsRegeneratedKeywordTheme":{"properties":{"keyword_theme":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Keyword Theme","description":"A theme that is not `current` and not in `existing`, or `null` when there is no new one. Keep the theme you had on a `null`.","example":"cracked screen fix"}},"type":"object","required":["keyword_theme"],"title":"GoogleAdsRegeneratedKeywordTheme","description":"One replacement keyword theme."},"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."},"GoogleAdsSetupKeywordThemes":{"properties":{"keyword_themes":{"items":{"type":"string"},"type":"array","title":"Keyword Themes","description":"Up to 25 themes Google suggests, in Google's order. Empty when Google could not answer.","example":["phone repair shop","screen replacement"]}},"type":"object","required":["keyword_themes"],"title":"GoogleAdsSetupKeywordThemes","description":"Google's keyword themes for the app's landing page."},"GoogleAdsSetupKeywordThemesRequest":{"properties":{"geo_targets":{"items":{"type":"string"},"type":"array","title":"Geo Targets","description":"Locations the campaign targets, as Google geo target constant IDs (`geoTargetConstants/1023191`, or just `1023191`). Entries in any other form are ignored, and with none left you get an empty list.","example":["geoTargetConstants/1007339"]},"language_code":{"type":"string","title":"Language Code","description":"Language the themes should be in, normally the `language_code` [Suggest Google Ads setup targeting](/api-reference/suggest-google-ads-setup-targeting) returned. A missing or unsupported code gets English themes.","default":"","example":"en"}},"additionalProperties":false,"type":"object","title":"GoogleAdsSetupKeywordThemesRequest"},"GoogleAdsSetupLocation":{"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/1007339"},"name":{"type":"string","title":"Name","description":"The location's own name.","example":"Dublin"},"canonical_name":{"type":"string","title":"Canonical Name","description":"The location's full path, including its region and country.","example":"Dublin,County Dublin,Ireland"},"country_code":{"type":"string","title":"Country Code","description":"ISO 3166-1 alpha-2 country code the location sits in.","example":"IE"},"reach":{"anyOf":[{"type":"string"},{"type":"integer"}],"title":"Reach","description":"Google's estimate of how many people can be reached in this location. Google sends this 64-bit integer as a string of digits, so parse it before comparing. It is the number `0` when Google gives no estimate.","example":"1300000"}},"type":"object","required":["resource_name","name","canonical_name","country_code","reach"],"title":"GoogleAdsSetupLocation","description":"One location Base44 suggests targeting."},"GoogleAdsSetupSuggestions":{"properties":{"geo_targets":{"items":{"$ref":"#/components/schemas/GoogleAdsSetupLocation"},"type":"array","title":"Geo Targets","description":"Up to 10 locations worth advertising in, one per place the model named. Empty when it couldn't tell.","example":[{"canonical_name":"Dublin,County Dublin,Ireland","country_code":"IE","name":"Dublin","reach":"1300000","resource_name":"geoTargetConstants/1007339"}]},"keyword_themes":{"items":{"type":"string"},"type":"array","title":"Keyword Themes","description":"Up to 25 search themes a buyer might type, best first, in the app's own language.","example":["phone screen repair","iphone battery replacement"]},"conversion_categories":{"items":{"type":"string"},"type":"array","title":"Conversion Categories","description":"Conversion goals worth tracking because the app already has the flow behind them, from `PURCHASE`, `ADD_TO_CART`, `BEGIN_CHECKOUT`, `SUBSCRIBE_PAID`, `SUBMIT_LEAD_FORM`, `BOOK_APPOINTMENT`, `REQUEST_QUOTE`, `CONTACT`, `PHONE_CALL_LEAD` and `SIGNUP`. Often empty.","example":["BOOK_APPOINTMENT","PHONE_CALL_LEAD"]},"language_code":{"type":"string","title":"Language Code","description":"Two-letter code of the language `keyword_themes` is written in, as the model reported it. Empty when it couldn't tell. Pass it to [Suggest Google Ads setup keyword themes](/api-reference/suggest-google-ads-setup-keyword-themes).","example":"en"},"limits":{"$ref":"#/components/schemas/GoogleAdsTargetingLimits","description":"The limits the themes and excluded terms you save have to fit."}},"type":"object","required":["geo_targets","keyword_themes","conversion_categories","language_code","limits"],"title":"GoogleAdsSetupSuggestions","description":"Suggested targeting and conversion goals for a first campaign."},"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."},"GoogleAdsTargetingLimits":{"properties":{"max_keyword_themes":{"type":"integer","title":"Max Keyword Themes","description":"Most keyword themes a campaign can have.","example":25},"max_excluded_terms":{"type":"integer","title":"Max Excluded Terms","description":"Most excluded search terms a campaign can have.","example":25},"term_max_len":{"type":"integer","title":"Term Max Len","description":"Most characters in one theme or excluded term.","example":80},"term_max_words":{"type":"integer","title":"Term Max Words","description":"Most words in one theme or excluded term.","example":10}},"type":"object","required":["max_keyword_themes","max_excluded_terms","term_max_len","term_max_words"],"title":"GoogleAdsTargetingLimits","description":"The limits a campaign's keyword themes and excluded terms have to fit."},"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."},"GoogleAdsVideoUpload":{"properties":{"video_id":{"type":"string","title":"Video Id","description":"The YouTube video ID. Pass it as a video on a campaign, exactly as you would the ID from a YouTube link. Empty while YouTube is still processing the upload, in which case poll with `upload_rn`.","example":"dQw4w9WgXcQ"},"upload_rn":{"type":"string","title":"Upload Rn","description":"Google's reference for the upload. Keep it until `video_id` is filled in, and pass it to [Poll a Google Ads video upload](/api-reference/poll-a-google-ads-video-upload).","example":"customers/1234567890/youTubeVideoUploads/AbC123xYz"}},"type":"object","required":["video_id","upload_rn"],"title":"GoogleAdsVideoUpload","description":"A video uploaded to YouTube for Google Ads."},"GoogleAdsVideoUploadPollRequest":{"properties":{"upload_rn":{"type":"string","title":"Upload Rn","description":"The `upload_rn` that [Upload a Google Ads video](/api-reference/upload-a-google-ads-video) returned for this app.","example":"customers/1234567890/youTubeVideoUploads/AbC123xYz"}},"type":"object","required":["upload_rn"],"title":"GoogleAdsVideoUploadPollRequest","description":"The reference returned by an upload that was still processing."},"GoogleAdsVideoUploadRequest":{"properties":{"file_url":{"type":"string","title":"File Url","description":"Address of the video file. It has to be `https` on a host Base44 fetches media from, such as an address on `base44.com` or `base44.app`, and the file can be at most 64 MB. Base44 does not fetch any other address and rejects it with a 400.","example":"https://storage.base44.com/gads-assets/spring-spot.mp4"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Title of the unlisted YouTube video. Only the first 100 characters are used. It is shown on the video itself, never in the ad. Leave it out for a generic title.","example":"Spring sale spot"}},"type":"object","required":["file_url"],"title":"GoogleAdsVideoUploadRequest","description":"A video file to upload to YouTube for Google Ads."},"GrepMatch":{"properties":{"path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path","description":"File the match is in, relative to the app root. `null` on a match line Base44 couldn't split into path, line and text, where the whole line lands in `text` instead.","example":"src/pages/Home.jsx"},"line":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Line","description":"1-based line number of the match, and `null` in the same case `path` is.","example":2},"text":{"type":"string","title":"Text","description":"The matching line. Always populated, so it's the field to fall back on.","example":"  return <h1>Hello</h1>;"}},"type":"object","required":["path","line","text"],"title":"GrepMatch","description":"One matching line."},"GrepResult":{"properties":{"matches":{"items":{"$ref":"#/components/schemas/GrepMatch"},"type":"array","title":"Matches","description":"Matching lines, capped at `max_results`.","example":[{"line":2,"path":"src/pages/Home.jsx","text":"  return <h1>Hello</h1>;"}]},"truncated":{"type":"boolean","title":"Truncated","description":"`true` when there was more to return: either more matches than `max_results`, or output that hit the 1 MB cap. One very long line can set this with few matches.","example":false},"returned_matches":{"type":"integer","title":"Returned Matches","description":"How many entries `matches` holds.","example":1}},"type":"object","required":["matches","truncated","returned_matches"],"title":"GrepResult","description":"The matches, and whether you got all of them."},"GroupMemberItem":{"properties":{"email":{"type":"string","title":"Email","description":"Email of the member.","example":"dana@acme.com"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id","description":"ID of the member's Base44 user.","example":"6820f41be7b91d003c45a20a"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name","description":"Member's name, or `null` when none is set.","example":"Dana Levi"},"profile_image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Profile Image Url","description":"URL of the member's profile image, or `null` when none is set.","example":"https://lh3.googleusercontent.com/a/ACg8ocJ2"}},"type":"object","required":["email"],"title":"GroupMemberItem"},"GroupMembersModifyRequest":{"properties":{"emails":{"items":{"type":"string"},"type":"array","maxItems":200,"minItems":1,"title":"Emails","description":"Emails of the members, 1 to 200.","example":["dana@acme.com","sam@acme.com"]}},"type":"object","required":["emails"],"title":"GroupMembersModifyRequest"},"GroupMembersPage":{"properties":{"members":{"items":{"$ref":"#/components/schemas/GroupMemberItem"},"type":"array","title":"Members","description":"Members on this page, sorted by email.","example":[{"email":"dana@acme.com","full_name":"Dana Levi","user_id":"6820f41be7b91d003c45a20a"}]},"total":{"type":"integer","title":"Total","description":"Number of members that match `search`, across all pages.","example":12},"has_more":{"type":"boolean","title":"Has More","description":"Whether more members follow this page.","example":false}},"type":"object","required":["members","total","has_more"],"title":"GroupMembersPage"},"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"},"IgnoreFindingPayload":{"properties":{"fingerprint":{"type":"string","maxLength":128,"minLength":1,"title":"Fingerprint","description":"The finding's `fingerprint`, as returned by [Get security scan](/api-reference/get-security-scan). Up to 128 characters.","example":"3f9a1c0e8b7d4a6f2e5c9b1d0a8f7e6c5b4a3d2e1f0c9b8a7d6e5f4c3b2a1d0e"},"finding_type":{"type":"string","maxLength":64,"minLength":1,"title":"Finding Type","description":"Which list in [Get security scan](/api-reference/get-security-scan) the finding came from. Send `rls` for `rls_recommendations`, `secret` for `hardcoded_secrets`, `backend_function` for `backend_functions`, `dependency` for `dependency_vulnerabilities`, or `static_code` for `static_code_findings`. Any other value is accepted and nothing is recorded.","example":"static_code"},"file_path":{"type":"string","maxLength":1024,"title":"File Path","description":"Path of the file the finding points at, kept with the ignore as a record of what was ignored. Up to 1,024 characters. Defaults to an empty string.","default":"","example":"src/pages/Orders.jsx"},"title":{"type":"string","maxLength":2000,"title":"Title","description":"Short name for the finding, kept with the ignore as a record of what was ignored. Up to 2,000 characters. Defaults to an empty string.","default":"","example":"Order lookup trusts a client-supplied ID"}},"additionalProperties":false,"type":"object","required":["fingerprint","finding_type"],"title":"IgnoreFindingPayload","description":"The security finding to ignore."},"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 conversation with the AI. 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."},"InitiateConnectionRequest":{"properties":{"integration_type":{"anyOf":[{"type":"string","enum":["googlecalendar","google_classroom","googledrive","gmail","googlesheets","googledocs","googleslides","googlebigquery","googlemeet","googletasks","googleads","google_analytics","google_search_console","slack","notion","salesforce","hubspot","linkedin","tiktok","instagram","discord","slackbot","wix","github","gitlab","bamboohr","dropbox","clickup","wrike","box","outlook","linear","airtable","microsoft_teams","share_point","one_drive","typeform","splitwise","hugging_face","calendly","contentful","supabase","snowflake","databricks","quickbooks","square"]},{"type":"string","maxLength":80,"minLength":1,"pattern":"^[a-z0-9_]+$"},{"type":"null"}],"title":"Integration Type","description":"Connector to connect through Base44's OAuth app, from `integration_type` in [List connectors](/api-reference/list-connectors). Send this or `connector_id`, not both.","example":"googlecalendar"},"scopes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Scopes","description":"OAuth scopes to ask the provider for. Scopes the app's current connection already has are kept unless `force_reconnect` is `true`. Defaults to an empty list, which asks for the connector's default scopes.","example":["https://www.googleapis.com/auth/calendar.readonly"]},"force_reconnect":{"type":"boolean","title":"Force Reconnect","description":"Whether to start a new authorization even when the app already has a working connection that covers the scopes. Send `true` to switch to another account. Applies only with `integration_type`. Defaults to `false`.","default":false,"example":false},"connection_config":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Connection Config","description":"Values the connector needs before authorization, keyed by the `name` of each field from [Get connector connection fields](/api-reference/get-connector-connection-fields). Leave it out for connectors that have no fields.","example":{"subdomain":"acme-prod"}},"connector_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Connector Id","description":"ID of a workspace connector that uses the workspace's own OAuth app, set up in the workspace settings. Send this or `integration_type`, not both.","example":"6820f3a4e7b91d003c45a1f9"},"notify_chat_on_success":{"type":"boolean","title":"Notify Chat On Success","description":"Whether a successful connection adds a message to the app's AI chat so the AI picks up wiring the connector into the app. Applies only with `integration_type`. Defaults to `false`.","default":false,"example":false}},"type":"object","title":"InitiateConnectionRequest"},"InitiateConnectionResponse":{"properties":{"redirect_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Redirect Url","description":"Link to open in a browser so the user can sign in to the provider and approve access. It expires after 10 minutes. Treat it as a credential. The value is `null` when no authorization was started.","example":"https://app.base44.com/api/external-auth/connect/3f9c2a7e5b8d4c1fa6e0d2b7c9a1e4f3"},"connection_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Connection Id","description":"ID of the connection being authorized. Pass it to [Get connector connection status](/api-reference/get-connector-connection-status). When `already_authorized` is `true` it's the existing connection, or `null` if you sent `connector_id`. The value is `null` when `error` is set.","example":"base44_6820f3a4e7b91d003c45a1f7"},"integration_type":{"anyOf":[{"type":"string","enum":["googlecalendar","google_classroom","googledrive","gmail","googlesheets","googledocs","googleslides","googlebigquery","googlemeet","googletasks","googleads","google_analytics","google_search_console","slack","notion","salesforce","hubspot","linkedin","tiktok","instagram","discord","slackbot","wix","github","gitlab","bamboohr","dropbox","clickup","wrike","box","outlook","linear","airtable","microsoft_teams","share_point","one_drive","typeform","splitwise","hugging_face","calendly","contentful","supabase","snowflake","databricks","quickbooks","square"]},{"type":"string","maxLength":80,"minLength":1,"pattern":"^[a-z0-9_]+$"},{"type":"null"}],"title":"Integration Type","description":"Connector being connected. Without `connector_id`, the value is `null` when `already_authorized` is `true` or `error` is set.","example":"googlecalendar"},"already_authorized":{"type":"boolean","title":"Already Authorized","description":"Whether the app already has a working connection that covers the requested scopes (`true`), so there's nothing to open, or not (`false`).","default":false,"example":false},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why no authorization was started, or `null` if one was. Either `different_user`, when another collaborator's account is already connected (with `integration_type`, send `force_reconnect` to replace it), or `service_unavailable`, when the provider can't be reached. Retry later.","example":"different_user"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message","description":"Readable explanation of `error`, or `null` when `error` is `null`.","example":"Integration googlecalendar is already authorized by a different user for this app."}},"type":"object","required":["redirect_url","connection_id"],"title":"InitiateConnectionResponse","description":"The start of a connector authorization, or why none was needed."},"InitiateTransferRequest":{"properties":{"new_owner_email":{"type":"string","format":"email","title":"New Owner Email","description":"Email address of the new owner. The invitation goes there, and they need a Base44 account with this email to accept it.","example":"jane@acme.com"},"destination":{"anyOf":[{"type":"string","enum":["same_workspace","external_email"]},{"type":"null"}],"title":"Destination","description":"Either `same_workspace`, to invite an owner, admin, editor, or member of the app's workspace, or `external_email`, to invite anyone. Defaults to `external_email`.","example":"external_email"},"disconnect_integrations":{"type":"boolean","title":"Disconnect Integrations","description":"Whether to disconnect all of the app's integrations when the recipient accepts into another workspace (`true`) or keep them connected (`false`). It's used only when you send neither `integration_decisions` nor `secret_decisions`, and has no effect when the recipient accepts into the app's current workspace. To disconnect integrations in that case, send `integration_decisions`. Defaults to `true`.","default":true,"example":true},"integration_decisions":{"anyOf":[{"items":{"$ref":"#/components/schemas/IntegrationTransferDecisionRequest"},"type":"array"},{"type":"null"}],"title":"Integration Decisions","description":"What happens to each of the app's integrations when the recipient accepts. An integration you leave out is disconnected, and so is one whose `can_keep_connected` is `false`, whatever you choose. If you send `secret_decisions` without this, it counts as empty, which is rejected when the app has integrations.","example":[{"action":"keep_connected","integration_type":"slack"}]},"secret_decisions":{"anyOf":[{"items":{"$ref":"#/components/schemas/SecretTransferDecisionRequest"},"type":"array"},{"type":"null"}],"title":"Secret Decisions","description":"What happens to each of the app's secrets when the recipient accepts. A secret you leave out needs a new value. Values never go in the request, and are copied on the server. If you send `integration_decisions` without this, it counts as empty, which is rejected when the app has secrets. When you send neither list, every secret keeps its value.","example":[{"action":"require_new_value","secret_name":"STRIPE_API_KEY"}]},"keep_sender_as_collaborator":{"type":"boolean","title":"Keep Sender As Collaborator","description":"Whether the app's current owner keeps editor access to the app after the transfer (`true`) or loses their direct access to it (`false`). An owner or admin of the workspace the app ends up in keeps access through that workspace either way. Defaults to `false`.","default":false,"example":false}},"type":"object","required":["new_owner_email"],"title":"InitiateTransferRequest","description":"The ownership transfer invitation to send."},"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."},"InstallStripeResponse":{"properties":{"already_installed":{"type":"boolean","title":"Already Installed","description":"Whether an existing usable installation was reused. Defaults to `false`.","default":false,"example":false},"claim_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Claim Url","description":"Stripe onboarding link, or `null` when no claim step is available. Treat this link as a credential. Defaults to `null`.","example":"https://dashboard.stripe.com/claim/example"}},"type":"object","title":"InstallStripeResponse","description":"Response for Stripe installation."},"IntegrationInfo":{"properties":{"integration_type":{"anyOf":[{"type":"string","enum":["googlecalendar","google_classroom","googledrive","gmail","googlesheets","googledocs","googleslides","googlebigquery","googlemeet","googletasks","googleads","google_analytics","google_search_console","slack","notion","salesforce","hubspot","linkedin","tiktok","instagram","discord","slackbot","wix","github","gitlab","bamboohr","dropbox","clickup","wrike","box","outlook","linear","airtable","microsoft_teams","share_point","one_drive","typeform","splitwise","hugging_face","calendly","contentful","supabase","snowflake","databricks","quickbooks","square"]},{"type":"string","maxLength":80,"minLength":1,"pattern":"^[a-z0-9_]+$"}],"title":"Integration Type","description":"Connector this connection is for.","example":"googlecalendar"},"auth_method":{"type":"string","enum":["oauth","platform_credentials"],"title":"Auth Method","description":"How the connector authenticates. Either `oauth`, for a provider account someone signed in with, or `platform_credentials`, for a connector that runs on Base44's own credentials.","default":"oauth","example":"oauth"},"user_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Email","description":"Email of the collaborator who made the connection. It's `Base44` for a `platform_credentials` connector on Base44's credentials and `null` for one on the app's own credentials. It's `Unknown` when the account no longer exists, and can be `null` when another collaborator made the connection.","example":"jane@acme.com"},"integration_account_identifier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Integration Account Identifier","description":"The provider account that's connected, such as its email or username. The value is `null` when the provider doesn't report one, the connection isn't active, or another collaborator made the connection.","example":"jane@acme.com"},"status":{"$ref":"#/components/schemas/IntegrationStatus","description":"State of the connection. One of `active`, `expired` (it needs to be authorized again), or `disconnected`.","example":"active"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At","description":"When the connection was authorized, as a UTC timestamp in ISO 8601 format, or `null` if unknown.","example":"2026-01-15T09:23:41Z"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"OAuth scopes the connection was granted.","example":["https://www.googleapis.com/auth/calendar.readonly"]},"requires_connection_config":{"type":"boolean","title":"Requires Connection Config","description":"Whether reconnecting needs values from [Get connector connection fields](/api-reference/get-connector-connection-fields) (`true`) or not (`false`).","default":false,"example":false},"connected_by_me":{"type":"boolean","title":"Connected By Me","description":"Whether you made the connection (`true`) or another collaborator did (`false`).","default":true,"example":true},"connection_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Connection Id","description":"ID of the underlying connection, or `null` for a disconnected one or a `platform_credentials` connector on Base44's credentials.","example":"base44_6820f3a4e7b91d003c45a1f7"},"connection_ownership":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Connection Ownership","description":"Who owns the connection. Either `app`, for a connection only this app uses, or `workspace`, for one shared by apps across the workspace. The value is `null` for a `platform_credentials` connector on Base44's credentials.","example":"app"}},"type":"object","required":["integration_type","user_email","integration_account_identifier","status","created_at","scopes"],"title":"IntegrationInfo"},"IntegrationPollingStatus":{"type":"string","enum":["ACTIVE","FAILED","PENDING"],"title":"IntegrationPollingStatus","description":"Enum for integration polling statuses."},"IntegrationPollingStatusResult":{"properties":{"status":{"$ref":"#/components/schemas/IntegrationPollingStatus","description":"State of the authorization. `PENDING` while it's still in progress, `ACTIVE` once the connection is ready to use, or `FAILED` if it was refused, expired, or couldn't be completed.","example":"ACTIVE"},"status_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Reason","description":"Why the authorization failed, when `status` is `FAILED` and you started it. The value is `null` otherwise.","example":"OAuth provider returned access_denied"}},"type":"object","required":["status"],"title":"IntegrationPollingStatusResult","description":"Where a connector authorization stands."},"IntegrationStatus":{"type":"string","enum":["active","expired","disconnected"],"title":"IntegrationStatus","description":"Enum for integration status values in the database."},"IntegrationTransferDecisionRequest":{"properties":{"integration_type":{"type":"string","minLength":1,"title":"Integration Type","description":"Integration type, from `transfer_package.integrations` in the preflight result.","example":"slack"},"action":{"type":"string","enum":["keep_connected","disconnect"],"title":"Action","description":"Either `keep_connected`, to leave the integration connected for the new owner, or `disconnect`.","example":"keep_connected"}},"type":"object","required":["integration_type","action"],"title":"IntegrationTransferDecisionRequest","description":"Sender decision for one app integration."},"IntegrationTypeInfo":{"properties":{"integration_type":{"type":"string","title":"Integration Type","description":"ID of the connector. Use it as `integration_type` on the app connector endpoints.","example":"googlecalendar"},"display_name":{"type":"string","title":"Display Name","description":"Name of the connector.","example":"Google Calendar"},"description":{"type":"string","title":"Description","description":"What the connector gives an app access to.","example":"Access and manage Google Calendar events"},"auth_method":{"type":"string","enum":["oauth","platform_credentials"],"title":"Auth Method","description":"How the connector authenticates. Either `oauth`, where someone signs in to the provider, or `platform_credentials`, where it runs on Base44's own credentials and has no authorization to start.","default":"oauth","example":"oauth"},"connection_config_fields":{"items":{"$ref":"#/components/schemas/ConnectionConfigField"},"type":"array","title":"Connection Config Fields","description":"Values to collect before authorization, sent as `connection_config`. Empty for most connectors.","example":[]},"connector_mode":{"$ref":"#/components/schemas/ConnectorMode","description":"Which ways the connector can be connected. `all` and `shared_only` connect through Base44's OAuth app with `integration_type`. `byo_shared_only` and `byo_shared_and_app_user` need a workspace connector with the workspace's own OAuth app, sent as `connector_id`. `app_user_only`, `all`, and `byo_shared_and_app_user` also let each of the app's users connect their own account.","default":"all","example":"all"},"subtitle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subtitle","description":"Short tagline, or `null` if the connector has none.","example":"Manage your schedule and calendar events."},"category":{"type":"string","title":"Category","description":"Group the connector is listed under.","default":"Other","example":"Project Management"},"display_status":{"type":"string","title":"Display Status","description":"Release stage of the connector. One of `active`, `beta`, or `coming_soon`.","default":"active","example":"active"},"icon_svg":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon Svg","description":"The connector's logo as inline SVG markup, or `null` if it has none.","example":"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">...</svg>"},"release_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Release Date","description":"When the connector was released, in `YYYY-MM-DD` format, or `null` if unknown.","example":"2025-11-30"},"popularity":{"type":"integer","title":"Popularity","description":"Ranking hint where a lower number is more prominent, starting at 1. `0` means the connector has no rank.","default":0,"example":2},"catalog_order":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Catalog Order","description":"Position in the catalog, or `null` for connectors listed after the ordered ones.","example":5},"example_scopes":{"items":{"type":"string"},"type":"array","title":"Example Scopes","description":"Scopes an app typically asks for.","default":[],"example":["https://www.googleapis.com/auth/calendar.readonly"]},"access_modes":{"anyOf":[{"$ref":"#/components/schemas/_AccessModesView"},{"type":"null"}],"description":"Scope sets for read-only and full access, or `null` if the connector doesn't offer the choice.","example":{"full_access":["https://www.googleapis.com/auth/calendar.readonly","https://www.googleapis.com/auth/calendar.events"],"read_only":["https://www.googleapis.com/auth/calendar.readonly"]}},"example_prompts":{"items":{"type":"string"},"type":"array","title":"Example Prompts","description":"Sample requests to the app's AI chat that use the connector.","default":[],"example":["Sync all my bookings directly to my Google Calendar."]},"is_public_client":{"type":"boolean","title":"Is Public Client","description":"Whether the provider's OAuth app authenticates with a client ID alone (`true`), so a workspace connector needs no client secret, or also needs a secret (`false`).","default":false,"example":false},"supports_base44_app_user":{"type":"boolean","title":"Supports Base44 App User","description":"Whether the app's users can connect their own accounts through Base44's OAuth app (`true`) or only through the workspace's own (`false`).","default":false,"example":false},"base44_app_user_allowed_scopes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Base44 App User Allowed Scopes","description":"Scopes Base44's OAuth app can grant the app's users, or `null` when it sets no limit.","example":["https://www.googleapis.com/auth/calendar.readonly"]}},"type":"object","required":["integration_type","display_name","description","connection_config_fields"],"title":"IntegrationTypeInfo"},"InviteMemberRequest":{"properties":{"email":{"type":"string","maxLength":254,"minLength":5,"title":"Email","description":"Email of the person to invite.","example":"dana@acme.com"},"role":{"type":"string","enum":["admin","editor","viewer"],"description":"Role the invitee gets when they accept. Defaults to `viewer`. `admin` needs the Business plan or higher.","default":"viewer","example":"editor"},"group_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Group Id","description":"ID of a custom group to add the invitee to when they accept. Get it from [List workspace groups](/api-reference/list-workspace-groups). Needs a plan that includes groups.","example":"68b4e1d2c9a7f3001e2d4b61"}},"type":"object","required":["email"],"title":"InviteMemberRequest","description":"An invitation to join a workspace."},"InviteUserResult":{"properties":{"email":{"type":"string","title":"Email","description":"The address, as you sent it.","example":"jane@acme.com"},"success":{"type":"boolean","title":"Success","description":"Whether the invitation was sent.","example":true},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why the invitation wasn't sent. Only present when `success` is `false`.","example":"User is not a member of this workspace."},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code","description":"`not_workspace_member` when an editor invitation went to someone outside the app's workspace. Send it again with `add_as_guest` set to add them. Absent for every other failure.","example":"not_workspace_member"},"has_pending_invitation":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Pending Invitation","description":"Whether that person already has an invitation to the workspace waiting. Only present with `not_workspace_member`.","example":false}},"type":"object","required":["email","success"],"title":"InviteUserResult","description":"What happened to one invitation."},"InviteUsersResponse":{"properties":{"results":{"items":{"$ref":"#/components/schemas/InviteUserResult"},"type":"array","title":"Results","description":"One entry per address you sent.","example":[{"email":"jane@acme.com","success":true},{"email":"sam@acme.com","error":"Builder already exists.","success":false}]}},"type":"object","required":["results"],"title":"InviteUsersResponse","description":"The outcome of each invitation, in the order you sent them."},"JsonLdEntity":{"properties":{"entity_name":{"type":"string","title":"Entity Name","description":"Name of the app entity this markup describes.","example":"Product"},"schema_type":{"type":"string","title":"Schema Type","description":"The Schema.org type Base44 matched the entity to.","example":"Product"},"confidence":{"type":"number","title":"Confidence","description":"How sure the match is, 0 to 1. Base44 leaves out any entity below 0.2.","example":0.85},"json_ld_template":{"additionalProperties":true,"type":"object","title":"Json Ld Template","description":"The JSON-LD Base44 would inject, with `{field}` placeholders naming the entity fields that fill it. Written verbatim into the page, so the placeholders are not substituted per record.","example":{"@context":"https://schema.org","@type":"Product","description":"{summary}","name":"{title}"}},"field_mapping":{"additionalProperties":true,"type":"object","title":"Field Mapping","description":"Which entity field fills each Schema.org property, keyed by property.","example":{"description":"summary","name":"title"}}},"type":"object","required":["entity_name","schema_type","confidence","json_ld_template","field_mapping"],"title":"JsonLdEntity","description":"One entity Base44 can describe with Schema.org markup."},"JsonLdPreview":{"properties":{"entities":{"items":{"$ref":"#/components/schemas/JsonLdEntity"},"type":"array","title":"Entities","description":"One entry per entity Base44 could match to a Schema.org type. Empty when none of the app's entities match one confidently.","example":[{"confidence":0.85,"entity_name":"Product","field_mapping":{"name":"title"},"json_ld_template":{"@context":"https://schema.org","@type":"Product","name":"{title}"},"schema_type":"Product"}]}},"type":"object","title":"JsonLdPreview","description":"The Schema.org markup Base44 would publish for this app."},"JsonLdPreviewResponse":{"properties":{"result":{"$ref":"#/components/schemas/JsonLdPreview","description":"The generated markup."}},"type":"object","required":["result"],"title":"JsonLdPreviewResponse","description":"The preview."},"KpiInstrument":{"properties":{"builder_prompt":{"type":"string","title":"Builder Prompt","description":"Prompt for the app builder that adds the missing analytics event. Present only while the app doesn't record `metric.event_name` yet.","example":"Track a lead_created event when a lead is saved on the Leads page."},"where":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Where","description":"Where in the app's code the outcome already happens. Omitted when the agent didn't find it.","example":"src/pages/Leads.jsx:42"},"requested_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Requested At","description":"When the build that adds the event was requested, as an ISO 8601 timestamp, or `null` if it hasn't been.","example":"2026-08-24T09:20:00+00:00"}},"type":"object","required":["builder_prompt"],"title":"KpiInstrument"},"KpiMetric":{"properties":{"template":{"type":"string","enum":["event_count","unique_sessions","unique_users","retention_dn"],"title":"Template","description":"How the KPI is counted. Either `\"event_count\"`, `\"unique_sessions\"`, `\"unique_users\"`, or `\"retention_dn\"`.","example":"event_count"},"event_name":{"type":"string","title":"Event Name","description":"Analytics event the KPI is measured against.","example":"lead_created"},"window_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Window Days","description":"Number of days the value is counted over. Either `7` or `30`.","example":7},"n":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"N","description":"Day the return is measured at, for a `retention_dn` KPI only.","example":7}},"type":"object","required":["template","event_name"],"title":"KpiMetric"},"KpiValue":{"properties":{"state":{"type":"string","enum":["measured","awaiting_data","window_too_short","not_tracked","unbound","unavailable"],"title":"State","description":"What the value means. `measured` is a full measurement. `awaiting_data` means the event started being recorded inside the KPI's window, so `value` covers less than the whole window. `window_too_short` means a retention KPI's event is recorded but no user has been around for `n` days yet. `not_tracked` means the app doesn't record the KPI's event yet. `unbound` means the KPI has no `metric`, so there's nothing to measure. `unavailable` means the analytics read failed, so try again later.","example":"measured"},"value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Value","description":"The measured number, present only when `state` is `measured` or `awaiting_data`. A count for most KPIs, and a percentage rounded to one decimal place for a `retention_dn` KPI.","example":42},"unit":{"anyOf":[{"type":"string","const":"percent"},{"type":"null"}],"title":"Unit","description":"`percent` on a measured `retention_dn` KPI. Absent otherwise, where `value` is a count.","example":"percent"},"first_seen":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Seen","description":"When the app first recorded the KPI's event, as a UTC ISO 8601 timestamp. Absent when `state` is `not_tracked`, `unbound`, or `unavailable`, except on a `retention_dn` KPI that isn't tracked, where it's `null`.","example":"2026-08-22T10:00:00Z"},"window_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Window Days","description":"Number of days `value` counts over, on a KPI that isn't `retention_dn`. Absent otherwise.","example":7},"n":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"N","description":"Day a `retention_dn` KPI measures the return at. Absent on other KPIs.","example":7},"cohort":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cohort","description":"Number of users a measured `retention_dn` KPI's percentage is out of. Absent otherwise.","example":120}},"type":"object","required":["state"],"title":"KpiValue","description":"What one KPI measures right now."},"KpiValuesResponse":{"properties":{"values":{"items":{"$ref":"#/components/schemas/KpiValue"},"type":"array","title":"Values","description":"One entry per KPI, in the same order as `kpis` in [Get Marketing Agent state](/api-reference/get-marketing-agent-state).","example":[{"first_seen":"2026-08-22T10:00:00Z","state":"measured","value":42,"window_days":7}]}},"type":"object","required":["values"],"title":"KpiValuesResponse"},"LastSEOScanResponse":{"properties":{"result":{"anyOf":[{"$ref":"#/components/schemas/SEOScanReport"},{"type":"null"}],"description":"The app's most recent scan, or `null` if it has never been scanned."}},"type":"object","title":"LastSEOScanResponse","description":"The stored scan."},"LatestTestRun":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the run.","example":"68a1c9b7f0b3d9001a7e5d04"},"app_id":{"type":"string","title":"App Id","description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"},"flow_id":{"type":"string","title":"Flow Id","description":"ID of the test this run belongs to.","example":"68a1c2e4f0b3d9001a7e5c21"},"flow_goal":{"type":"string","title":"Flow Goal","description":"The test's goal when the run started.","example":"Sign up, create a project called Launch, and check that it appears on the dashboard."},"flow_role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flow Role","description":"Role the run signs in as, including one Base44 picked from the test's name or goal. Set once the run starts. `null` when the run uses no role.","example":"admin"},"site_url":{"type":"string","title":"Site Url","description":"URL the run opened.","example":"https://preview-6820f3a4e7b91d003c45a1f2.base44.app/?_preview_token=FH-j7wHS7IR_fC1tUh4wCd_fNV1XGC479cuqNSYi9mA"},"status":{"type":"string","enum":["pending","running","analyzing","success","failed","timeout","cancelled","paused"],"title":"Status","description":"Where the run is. `pending`, `running` and `analyzing` mean it's still going. `success` means the browser session finished, and `failed` means it didn't. `timeout`, `cancelled` and `paused` mean the run stopped early. The test passed when `goal_accomplished` is `true` and `failure_reason` isn't `platform` or `internal`.","example":"success"},"actions":{"items":{"$ref":"#/components/schemas/TestRunStep"},"type":"array","title":"Actions","description":"Steps the agent took. Empty until the run finishes.","example":[{"step_summary":"Clicked Sign up in the header to reach the registration form.","step_title":"Opened the sign-up form"}]},"result_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Result Message","description":"The agent's account of how the run went, or `null` before the run finishes.","example":"Created the project and found it on the dashboard."},"goal_accomplished":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Goal Accomplished","description":"Whether the agent accomplished the goal, or `null` before the run finishes. Can be `true` on a run whose `failure_reason` is `platform` or `internal`, which counts as a technical failure.","example":true},"live_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Live Status","description":"Short progress message for display, or `null` before the run starts.","example":"Analyzing results"},"analysis_status":{"anyOf":[{"type":"string","enum":["pending","completed","failed"]},{"type":"null"}],"title":"Analysis Status","description":"Progress of the run's report. `completed` means [Get test run report](/api-reference/get-test-run-report) has it. `null` before the run reaches analysis.","example":"completed"},"failure_reason":{"anyOf":[{"type":"string","enum":["app","platform","internal","out_of_credits"]},{"type":"null"}],"title":"Failure Reason","description":"Why the run didn't pass, or `null` when no reason was recorded. `app` is a problem in your app. `platform` and `internal` are problems on Base44's side, so rerun the test. `out_of_credits` comes with status `paused`.","example":"app"},"credits_charged":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Credits Charged","description":"Credits the run cost, or `null` before it's charged.","example":1.5},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At","description":"When the browser session started, as an ISO 8601 UTC timestamp, or `null` while pending.","example":"2026-09-28T10:16:02+00:00"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At","description":"When the run ended, as an ISO 8601 UTC timestamp, or `null` while it's still going.","example":"2026-09-28T10:18:40+00:00"},"created_date":{"type":"string","title":"Created Date","description":"When the run was requested, as an ISO 8601 UTC timestamp.","example":"2026-09-28T10:16:00+00:00"},"updated_date":{"type":"string","title":"Updated Date","description":"When the run last changed, as an ISO 8601 UTC timestamp.","example":"2026-09-28T10:18:40+00:00"},"analysis":{"anyOf":[{"$ref":"#/components/schemas/TestRunReport"},{"type":"null"}],"description":"The run's report, the same one [Get test run report](/api-reference/get-test-run-report) returns, or `null` while the run has none."}},"type":"object","required":["id","app_id","flow_id","flow_goal","flow_role","site_url","status","actions","result_message","goal_accomplished","live_status","analysis_status","failure_reason","credits_charged","started_at","completed_at","created_date","updated_date","analysis"],"title":"LatestTestRun","description":"A test's most recent run, with its report."},"LatestTestRuns":{"additionalProperties":{"$ref":"#/components/schemas/LatestTestRun"},"type":"object","title":"LatestTestRuns","description":"Each test's most recent run, keyed by the test's ID."},"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"},"billing_cadence":{"type":"string","title":"Billing Cadence","description":"How often the campaign will be charged once launched: `3d`, `1w`, `2w`, or `1m`. Answered before the account exists so the billing terms shown at launch match how the account is actually charged.","example":"1w"},"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","billing_cadence","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."},"LaunchedRestore":{"properties":{"entity_name":{"type":"string","title":"Entity Name","description":"Entity being restored.","example":"Invoice"},"operation_id":{"type":"string","title":"Operation Id","description":"ID of this entity's part of the restore, as `operation_id` in Get data restore status.","example":"68a1c2f0e4f5a6b7c8d9e1a2"}},"type":"object","required":["entity_name","operation_id"],"title":"LaunchedRestore","description":"One entity whose restore started."},"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."},"ListConversationUsersResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ConversationUserSummary"},"type":"array","title":"Items","description":"The users on this page."},"total":{"type":"integer","title":"Total","description":"Total number of matching users.","example":42}},"type":"object","required":["items","total"],"title":"ListConversationUsersResponse","description":"A page of the app users who have conversations with the app's agents."},"ListDirectoryResult":{"properties":{"entries":{"items":{"$ref":"#/components/schemas/DirectoryEntry"},"type":"array","title":"Entries","description":"Files and directories, capped at 1000 entries.","example":[{"name":"pages","path":"src/pages","type":"directory"},{"name":"Home.jsx","path":"src/pages/Home.jsx","size":214,"type":"file"}]},"truncated":{"type":"boolean","title":"Truncated","description":"`true` when the listing hit the 1000-entry cap. Narrow it with `path` or a smaller `max_depth`.","example":false}},"type":"object","required":["entries","truncated"],"title":"ListDirectoryResult","description":"The listing, and whether you got all of it."},"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."},"ListIntegrationTypesResponse":{"properties":{"integrations":{"items":{"$ref":"#/components/schemas/IntegrationTypeInfo"},"type":"array","title":"Integrations","description":"Every connector available to you. Sort by `catalog_order` to list them the way Base44 does.","example":[{"auth_method":"oauth","catalog_order":5,"category":"Project Management","connection_config_fields":[],"connector_mode":"all","description":"Access and manage Google Calendar events","display_name":"Google Calendar","integration_type":"googlecalendar"}]}},"type":"object","required":["integrations"],"title":"ListIntegrationTypesResponse","description":"The connectors available to you."},"ListIntegrationsResponse":{"properties":{"integrations":{"items":{"$ref":"#/components/schemas/IntegrationInfo"},"type":"array","title":"Integrations","description":"One entry per connector the app has a connection for.","example":[{"auth_method":"oauth","connected_by_me":true,"connection_id":"base44_6820f3a4e7b91d003c45a1f7","connection_ownership":"app","created_at":"2026-01-15T09:23:41Z","integration_account_identifier":"jane@acme.com","integration_type":"googlecalendar","requires_connection_config":false,"scopes":["https://www.googleapis.com/auth/calendar.readonly"],"status":"active","user_email":"jane@acme.com"}]}},"type":"object","required":["integrations"],"title":"ListIntegrationsResponse","description":"The app's connector connections."},"ListingFieldChange":{"properties":{"old":{"title":"Old","description":"Current value.","example":"1.0.0"},"new":{"title":"New","description":"Value after the update is approved.","example":"1.1.0"}},"type":"object","required":["old","new"],"title":"ListingFieldChange","description":"One listing detail that changes when the update is approved."},"LlmsTxtPreview":{"properties":{"content":{"type":"string","title":"Content","description":"The full file, ready to serve. Base44 generates it from the app's name, description and discovered routes, so it changes as the app does.","example":"# Acme CRM\n\n> Track leads and deals in one place.\n\n## Pages\n\n- [Home](https://acme-crm.base44.app/): Main page\n- [Pricing](https://acme-crm.base44.app/pricing): Pricing page\n"}},"type":"object","required":["content"],"title":"LlmsTxtPreview","description":"The llms.txt Base44 would publish for this app."},"LlmsTxtPreviewResponse":{"properties":{"result":{"$ref":"#/components/schemas/LlmsTxtPreview","description":"The generated file."}},"type":"object","required":["result"],"title":"LlmsTxtPreviewResponse","description":"The preview."},"LogoUrlResponse":{"properties":{"logo_url":{"type":"string","title":"Logo Url","description":"Public URL of the generated logo image.","example":"https://storage.base44.com/6820f3a4e7b91d003c45a1f2/3f2504e0_logo.png"}},"type":"object","required":["logo_url"],"title":"LogoUrlResponse","description":"A newly generated logo image."},"MainBranchProtection":{"properties":{"protected":{"type":"boolean","title":"Protected","description":"Whether the app's main branch is protected. While it is, changes to main have to go through a branch that you merge back.","example":true}},"additionalProperties":false,"type":"object","required":["protected"],"title":"MainBranchProtection"},"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"},"MarketingAgentStateResponse":{"properties":{"status":{"type":"string","enum":["idle","provisioning","analyzing","ready","error"],"title":"Status","description":"State of the latest run. Either `\"idle\"` before any run, `\"provisioning\"` while the workspace's Marketing Agent is created, `\"analyzing\"` while a run is working, `\"ready\"` once it finished, or `\"error\"` if it failed. A run that stops reporting for 15 minutes reads as `\"error\"`.","example":"ready"},"run_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Run Id","description":"ID of the latest run, or `null` if no run has started. Compare it between polls to tell a new run from the one you started.","example":"68a1c9b7f0b3d9001a7e5d04"},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error","description":"Why the latest run failed, or `null` if it didn't.","example":"The scan did not complete. Retry the setup."},"setup_stage":{"type":"string","enum":["not_started","scanning","channels","kpis","budget","generating","complete"],"title":"Setup Stage","description":"How far setup has got. Either `\"not_started\"`, `\"scanning\"`, `\"channels\"`, `\"kpis\"`, `\"budget\"`, `\"generating\"`, or `\"complete\"`. At `channels`, `kpis`, and `budget` the agent is waiting for an answer.","example":"channels"},"reset_epoch":{"type":"integer","title":"Reset Epoch","description":"Number of times the plan was reset. Send it back as `reset_epoch` when you answer a setup step.","example":0},"channels":{"items":{"$ref":"#/components/schemas/MarketingChannel"},"type":"array","title":"Channels","description":"The marketing channels the agent proposed. Empty until the opening scan finishes."},"channels_revision_suggestion":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Channels Revision Suggestion","description":"A change to the channels the agent offers to make, or `null` if it offers none. Send it as a message to accept it.","example":"Swap LinkedIn for Product Hunt"},"kpis":{"items":{"$ref":"#/components/schemas/MarketingKpi"},"type":"array","title":"Kpis","description":"The KPIs the agent proposed. Empty until the opening scan finishes."},"kpis_revision_suggestion":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kpis Revision Suggestion","description":"A change to the KPIs the agent offers to make, or `null` if it offers none. Send it as a message to accept it.","example":"Track paid conversions instead of signups"},"strategy":{"anyOf":[{"$ref":"#/components/schemas/MarketingStrategy"},{"type":"null"}],"description":"The marketing strategy, or `null` until setup writes one."},"suggestions":{"items":{"$ref":"#/components/schemas/MarketingSuggestion"},"type":"array","title":"Suggestions","description":"Growth ideas the agent generated for the app."},"budget":{"anyOf":[{"$ref":"#/components/schemas/MarketingBudget"},{"type":"null"}],"description":"The marketing budget given during setup, or `null` if none was given yet."},"competitor_research_opt_in":{"type":"boolean","title":"Competitor Research Opt In","description":"Whether competitor research runs alongside the strategy.","example":true},"competitor_research_answer":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Competitor Research Answer","description":"Whether competitor research was accepted (`true`) or declined (`false`), or `null` if the question wasn't answered yet.","example":true},"setup_paused":{"type":"boolean","title":"Setup Paused","description":"Whether setup is paused because the user asked the agent to stop. Sending a message resumes it.","example":false},"competitors":{"items":{"$ref":"#/components/schemas/MarketingCompetitor"},"type":"array","title":"Competitors","description":"Competitors found by the research, with verified URLs."},"competitor_scan_status":{"anyOf":[{"type":"string","enum":["running","done","failed"]},{"type":"null"}],"title":"Competitor Scan Status","description":"State of the competitor research. Either `\"running\"`, `\"done\"`, or `\"failed\"`, or `null` if it never ran.","example":"done"},"cmo_agent_app_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cmo Agent App Id","description":"ID of the workspace's Marketing Agent app your conversation runs on, or `null` if you haven't talked to it on this app yet.","example":"68a1c2e4f0b3d9001a7e5c21"}},"type":"object","required":["status","run_id","last_error","setup_stage","reset_epoch","channels","channels_revision_suggestion","kpis","kpis_revision_suggestion","strategy","suggestions","budget","competitor_research_opt_in","competitor_research_answer","setup_paused","competitors","competitor_scan_status","cmo_agent_app_id"],"title":"MarketingAgentStateResponse","description":"The Marketing Agent's plan for an app and the state of its latest run."},"MarketingBudget":{"properties":{"description":{"type":"string","title":"Description","description":"The budget as it was stated.","example":"$500 a month"},"band":{"anyOf":[{"type":"string","enum":["none","low","mid","high"]},{"type":"null"}],"title":"Band","description":"Budget band that selects each KPI's target. Either `\"none\"`, `\"low\"`, `\"mid\"`, or `\"high\"`. Omitted when the budget was stated in free text.","example":"mid"}},"type":"object","required":["description"],"title":"MarketingBudget"},"MarketingChannel":{"properties":{"name":{"type":"string","title":"Name","description":"Name of the channel.","example":"Reddit communities"},"why":{"type":"string","title":"Why","description":"Why this channel fits the app.","example":"Freelancers already trade CRM tips in r/freelance."},"tactics":{"items":{"type":"string"},"type":"array","title":"Tactics","description":"Concrete tactics for the channel. Up to 3.","example":["Share a build-in-public thread","Answer lead-tracking questions"]},"priority":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Priority","description":"How much to invest in this channel, from `1` to `5`, where `5` means invest here first. Omitted when the agent didn't rank it.","example":5}},"type":"object","required":["name","why","tactics"],"title":"MarketingChannel"},"MarketingCompetitor":{"properties":{"name":{"type":"string","title":"Name","description":"Name of the competitor.","example":"HubSpot"},"url":{"type":"string","title":"Url","description":"URL of the competitor's site.","example":"https://www.hubspot.com"},"angle":{"type":"string","title":"Angle","description":"How the competitor positions itself.","example":"A full CRM suite for sales teams."},"gap":{"type":"string","title":"Gap","description":"What the competitor leaves open for this app.","example":"Too heavy for a solo freelancer."}},"type":"object","required":["name","url","angle","gap"],"title":"MarketingCompetitor"},"MarketingKpi":{"properties":{"name":{"type":"string","title":"Name","description":"The KPI in one measurable line.","example":"Weekly signups"},"why":{"type":"string","title":"Why","description":"Why this KPI matters for the app.","example":"Signups are the first sign the Reddit threads convert."},"baseline":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Baseline","description":"Current value, when the agent could tell it from the app. Omitted otherwise.","example":"About 10 a week"},"target":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target","description":"Target at the recommended budget, with a time horizon.","example":"50 a week in 90 days"},"targets":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Targets","description":"The target at each budget band, keyed by `none`, `low`, `mid`, and `high`. A band that's missing means spend at that level can't move this KPI.","example":{"mid":"50 a week in 90 days","none":"20 a week in 90 days"}},"target_values":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Target Values","description":"The number inside each band's target, keyed like `targets`.","example":{"mid":50,"none":20}},"metric":{"anyOf":[{"$ref":"#/components/schemas/KpiMetric"},{"type":"null"}],"description":"How the KPI is measured against the app's analytics. Omitted on KPIs created before measurement was added."},"instrument":{"anyOf":[{"$ref":"#/components/schemas/KpiInstrument"},{"type":"null"}],"description":"How to start recording the KPI's event. Omitted when the app already records it."}},"type":"object","required":["name","why"],"title":"MarketingKpi"},"MarketingStrategy":{"properties":{"summary":{"type":"string","title":"Summary","description":"The strategy in up to 2 sentences.","example":"Win early freelancers in the communities they already use, then turn them into referrals."},"positioning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Positioning","description":"How the app is positioned against its competitors.","example":"The CRM for freelancers who never wanted a CRM."},"channel_plan":{"items":{"$ref":"#/components/schemas/MarketingChannel"},"type":"array","title":"Channel Plan","description":"The channels the strategy invests in."},"key_metrics":{"items":{"type":"string"},"type":"array","title":"Key Metrics","description":"The metrics the strategy is judged by.","example":["Weekly signups","Week-one retention"]},"budget_fit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Budget Fit","description":"How the strategy fits the workspace's stated budget.","example":"Organic first, with $100 a month for Reddit promoted posts."}},"type":"object","required":["summary","channel_plan","key_metrics"],"title":"MarketingStrategy"},"MarketingSuggestion":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the suggestion.","example":"68a1d0c2f0b3d9001a7e5e10"},"title":{"type":"string","title":"Title","description":"Short title of the growth idea.","example":"Add a referral link to the dashboard"},"builder_prompt":{"type":"string","title":"Builder Prompt","description":"Prompt to send to the app builder to build the idea.","example":"Add a Refer a friend card to the dashboard with a copyable invite link."},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"Kind of growth idea.","example":"Referral"},"rationale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rationale","description":"Why the idea should help.","example":"Freelancers recommend tools to each other."},"impact":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Impact","description":"Expected impact. Either `\"high\"`, `\"medium\"`, or `\"low\"`.","example":"high"},"effort":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Effort","description":"Rough build effort. Either `\"low\"`, `\"medium\"`, or `\"high\"`.","example":"low"},"dismissed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Dismissed","description":"Whether someone dismissed the suggestion.","example":false}},"type":"object","required":["id","title","builder_prompt"],"title":"MarketingSuggestion"},"MembersDateRangeFilter":{"properties":{"gte":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Gte","description":"Earliest time to include, in ISO 8601.","example":"2026-09-01T00:00:00Z"},"lte":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Lte","description":"Latest time to include, in ISO 8601.","example":"2026-09-30T23:59:59Z"}},"additionalProperties":false,"type":"object","title":"MembersDateRangeFilter","description":"Inclusive bounds on a time. Set either bound, or both."},"MembersListPage":{"properties":{"members":{"items":{"$ref":"#/components/schemas/WorkspaceMemberRow"},"type":"array","title":"Members","description":"The people on this page.","example":[{"access_state":"granted","agents_owned":1,"agents_shared":0,"apps_owned":4,"apps_shared":0,"assigned_via":"direct","credit_limit":500,"credits_used":128.5,"email":"dana@acme.com","full_name":"Dana Levi","is_default_limit":false,"last_active":"2026-10-03T14:12:08.512000","last_seen":"2026-10-05T08:41:22.004000","role":"editor","status":"active","user_id":"66f1c2a9b7e3d4001f8a2c55"}]},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass as `cursor` to get the next page. `null` on the last page.","example":"NTA="},"has_more":{"type":"boolean","title":"Has More","description":"`true` when there's another page.","default":false,"example":true},"total":{"type":"integer","title":"Total","description":"Number of people matching the filters, across all pages.","default":0,"example":137},"monthly_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Monthly Limit","description":"The workspace's monthly credits, or `null` when they're unlimited.","example":10000},"default_member_credit_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Default Member Credit Limit","description":"The workspace's default member credit limit, or `null` when none is set.","example":300}},"type":"object","required":["members","next_cursor","has_more","total","monthly_limit","default_member_credit_limit"],"title":"MembersListPage","description":"One page of workspace members."},"MembersListRequest":{"properties":{"search":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Search","description":"Text to match, ignoring case, anywhere in a member's email, name, role, or status.","example":"acme.com"},"search_terms":{"anyOf":[{"items":{"type":"string","maxLength":256},"type":"array","maxItems":200},{"type":"null"}],"title":"Search Terms","description":"More text to match the same way as `search`, up to 200 terms. A member matches when any term does.","example":["dana@acme.com","sam@acme.com"]},"sort_by":{"anyOf":[{"type":"string","enum":["member","credits","apps","agents","last_active","role"]},{"type":"null"}],"title":"Sort By","description":"Column to sort by: `member` (name, or email when there's no name), `credits` (`credits_used`), `apps` and `agents` (owned plus shared), `last_active`, or `role` (owner, admin, editor, viewer, guest). Needs `sort_dir`.","example":"last_active"},"sort_dir":{"anyOf":[{"type":"string","enum":["asc","desc"]},{"type":"null"}],"title":"Sort Dir","description":"Sort direction. Needs `sort_by`.","example":"desc"},"roles":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":20},{"type":"null"}],"title":"Roles","description":"Only members with one of these roles: `owner`, `admin`, `editor`, `viewer`, or `guest`.","example":["admin","editor"]},"invite_statuses":{"anyOf":[{"items":{"type":"string","enum":["joined","pending","expired"]},"type":"array","maxItems":3},{"type":"null"}],"title":"Invite Statuses","description":"Only people in one of these states: `joined`, `pending` (invited and not yet joined), or `expired` (the invitation lapsed).","example":["joined"]},"credit_limit_set":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Credit Limit Set","description":"`true` for only members with their own credit limit, `false` for only members without one. A member on the workspace default counts as not having their own.","example":true},"apps_owned":{"anyOf":[{"$ref":"#/components/schemas/MembersNumberRangeFilter"},{"type":"null"}],"description":"Only members whose `apps_owned` plus `apps_shared` is in this range."},"agents_owned":{"anyOf":[{"$ref":"#/components/schemas/MembersNumberRangeFilter"},{"type":"null"}],"description":"Only members whose `agents_owned` plus `agents_shared` is in this range."},"last_active":{"anyOf":[{"$ref":"#/components/schemas/MembersDateRangeFilter"},{"type":"null"}],"description":"Only members whose `last_active` is in this range. Members who never used credits don't match."},"cursor":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Cursor","description":"`next_cursor` from the previous page. Leave it out for the first page.","example":"NTA="},"page_size":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Page Size","description":"How many people to return, 1 to 100. Defaults to 50.","default":50,"example":50}},"additionalProperties":false,"type":"object","title":"MembersListRequest","description":"One page of a members list."},"MembersNumberRangeFilter":{"properties":{"gte":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gte","description":"Lowest count to include.","example":1},"lte":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lte","description":"Highest count to include.","example":10}},"additionalProperties":false,"type":"object","title":"MembersNumberRangeFilter","description":"Inclusive bounds on a count. Set either bound, or both."},"MentionableUser":{"properties":{"email":{"type":"string","title":"Email","description":"Email to pass in `mentioned_emails`.","example":"dana@acme.com"},"name":{"type":"string","title":"Name","description":"Name of the person, or their email when they have no name.","example":"Dana Levi"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url","description":"URL of the person's profile image, or `null` when they have none.","example":"https://lh3.googleusercontent.com/a/ACg8ocJ2"}},"type":"object","required":["email","name"],"title":"MentionableUser","description":"Someone a comment can mention."},"MentionableUsers":{"items":{"$ref":"#/components/schemas/MentionableUser"},"type":"array","title":"MentionableUsers","description":"The people a comment on the app can mention."},"MergedBranch":{"properties":{"branch":{"$ref":"#/components/schemas/BranchSummary","description":"The branch, now with `status` set to `merged`."},"merge_message_id":{"type":"string","title":"Merge Message Id","description":"ID of the message Base44 added to main's conversation to summarize the merge. Find it with [Read conversation messages](/api-reference/read-conversation-messages).","example":"7f3a1c88-52d4-4a0e-9b31-2c6f0d8e4a19"}},"type":"object","required":["branch","merge_message_id"],"title":"MergedBranch","description":"The merged branch."},"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"},"MessageQueue":{"properties":{"items":{"items":{"$ref":"#/components/schemas/QueuedMessage"},"type":"array","title":"Items","description":"The queued messages, next to run first. Holds at most 7.","example":[{"branch_id":"68f1a2b3c4d5e6f708192a3b","content":"Add a contact form to the home page","created_at":"2026-10-05T14:30:00.123456+00:00","id":"3b9d2f6e-8c41-4a7e-9f05-1d2c3b4a5e6f"}]},"is_paused":{"type":"boolean","title":"Is Paused","description":"Whether the queue is paused. While it is, queued messages don't run.","example":false}},"type":"object","required":["items","is_paused"],"title":"MessageQueue","description":"The messages waiting to run, in the order they run."},"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."},"OrganizationConnectorListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/OrganizationConnectorResponse"},"type":"array","title":"Items","description":"Every connector in the workspace.","example":[{"client_id":"1234567890-abc123def456.apps.googleusercontent.com","connection_config":{},"credential_source":"byo","id":"6820f3a4e7b91d003c45a1f9","integration_type":"googlecalendar","name":"Sales Google Calendar","organization_id":"67e0b12c4d8a3f005b21c9e4","scopes":["https://www.googleapis.com/auth/calendar.readonly"],"uses_oauth_gateway":true}]},"total":{"type":"integer","title":"Total","description":"Number of connectors in `items`.","example":1},"gateway_redirect_uri":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Redirect Uri","description":"Single redirect URI to register for connectors with `uses_oauth_gateway` set to `true`. Left out when the gateway callback isn't available to you.","example":"https://connectors.base44.com/api/oauth/callback"}},"type":"object","required":["items","total"],"title":"OrganizationConnectorListResponse","description":"The workspace's connectors."},"OrganizationConnectorResponse":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the workspace connector. Pass it as `connector_id` to [Start connector connection](/api-reference/start-connector-connection).","example":"6820f3a4e7b91d003c45a1f9"},"organization_id":{"type":"string","title":"Organization Id","description":"ID of the workspace.","example":"67e0b12c4d8a3f005b21c9e4"},"integration_type":{"type":"string","title":"Integration Type","description":"Connector it's for.","example":"googlecalendar"},"name":{"type":"string","title":"Name","description":"Name of the workspace connector.","example":"Sales Google Calendar"},"credential_source":{"type":"string","enum":["byo","base44"],"description":"Whose OAuth app it runs on: `byo` for the workspace's own, or `base44` for Base44's.","default":"byo","example":"byo"},"client_id":{"type":"string","title":"Client Id","description":"Client ID of the workspace's OAuth app. Empty for a connector on Base44's OAuth app.","example":"1234567890-abc123def456.apps.googleusercontent.com"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"OAuth scopes connections through it ask for.","example":["https://www.googleapis.com/auth/calendar.readonly"]},"connection_config":{"additionalProperties":{"type":"string"},"type":"object","title":"Connection Config","description":"Connection values saved on it, keyed by field name. Empty when the connector needs none.","example":{}},"uses_oauth_gateway":{"type":"boolean","title":"Uses Oauth Gateway","description":"Whether new connections return to the single gateway redirect URI (`true`) or to the per-app ones (`false`). [Get OAuth redirect URIs](/api-reference/get-oauth-redirect-uris) says which to register.","default":false,"example":true}},"type":"object","required":["id","organization_id","integration_type","name","client_id","scopes"],"title":"OrganizationConnectorResponse","description":"A workspace connector. The client secret is never returned."},"OwnershipTransferIntegrationSummary":{"properties":{"integration_type":{"type":"string","title":"Integration Type","description":"Integration type. Send it as `integration_type` in `integration_decisions`.","example":"slack"},"display_name":{"type":"string","title":"Display Name","description":"Name of the integration as shown in the builder.","example":"Slack"},"status":{"type":"string","title":"Status","description":"Connection status. Either `active`, `expired`, or `connected`.","example":"active"},"can_keep_connected":{"type":"boolean","title":"Can Keep Connected","description":"Whether the integration can stay connected for the new owner (`true`), or always disconnects on acceptance because it uses the current owner's own account (`false`).","default":true,"example":true}},"type":"object","required":["integration_type","display_name","status"],"title":"OwnershipTransferIntegrationSummary","description":"Safe sender-facing integration summary."},"OwnershipTransferPackageSummary":{"properties":{"mode":{"type":"string","title":"Mode","description":"How the app transfers, which is always `transfer_as_built`.","default":"transfer_as_built","example":"transfer_as_built"},"app":{"$ref":"#/components/schemas/OwnershipTransferPreflightAppSummary","description":"The app being transferred."},"recipient_email":{"type":"string","title":"Recipient Email","description":"Email address the invitation would go to.","example":"jane@acme.com"},"existing_account_required":{"type":"boolean","title":"Existing Account Required","description":"Whether the recipient needs a Base44 account to accept, which is always `true`. They can sign up after the invitation is sent.","default":true,"example":true},"target_workspace":{"type":"string","title":"Target Workspace","description":"Where the app goes by default, which is always `recipient_personal_workspace`. The recipient can choose another workspace when they accept.","default":"recipient_personal_workspace","example":"recipient_personal_workspace"},"active_integration_count":{"type":"integer","title":"Active Integration Count","description":"Number of entries in `integrations`.","default":0,"example":1},"integrations":{"items":{"$ref":"#/components/schemas/OwnershipTransferIntegrationSummary"},"type":"array","title":"Integrations","description":"The app's connected integrations."},"secrets":{"items":{"$ref":"#/components/schemas/OwnershipTransferSecretSummary"},"type":"array","title":"Secrets","description":"The app's secrets, without their values."},"has_google_ads_account":{"type":"boolean","title":"Has Google Ads Account","description":"Whether the app has a Google Ads account that transfers with it (`true`) or not (`false`). The new owner has to set up its billing again, and any outstanding balance has to be settled before the recipient can accept.","default":false,"example":false}},"type":"object","required":["app","recipient_email"],"title":"OwnershipTransferPackageSummary","description":"Current v1 transfer package summary."},"OwnershipTransferPreflightAppSummary":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"},"name":{"type":"string","title":"Name","description":"Name of the app.","example":"Acme CRM"},"source_workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Workspace Id","description":"ID of the workspace the app is in now.","example":"67f2c8e01a3b5d004e92d7a1"},"source_owner_id":{"type":"string","title":"Source Owner Id","description":"ID of the Base44 user who owns the app now.","example":"6820f3a4e7b91d003c45a1f0"}},"type":"object","required":["id","name","source_owner_id"],"title":"OwnershipTransferPreflightAppSummary","description":"Safe app summary for sender-side transfer review."},"OwnershipTransferPreflightIssue":{"properties":{"code":{"type":"string","title":"Code","description":"Machine-readable reason, for example `TRANSFER_ALREADY_PENDING`, `CANNOT_TRANSFER_TO_SELF`, `NOT_APP_OWNER`, or `NEW_OWNER_NOT_FOUND`.","example":"TRANSFER_ALREADY_PENDING"},"severity":{"type":"string","title":"Severity","description":"Either `blocker` or `warning`.","example":"blocker"},"message":{"type":"string","title":"Message","description":"Human-readable explanation of the issue.","example":"A pending ownership transfer already exists for Acme CRM."},"field":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Field","description":"Name of the input or app field the issue is about, or `null` when it isn't about one.","example":"new_owner_email"},"details":{"additionalProperties":true,"type":"object","title":"Details","description":"Extra information about the issue, as a JSON object. It's usually empty.","example":{}}},"type":"object","required":["code","severity","message"],"title":"OwnershipTransferPreflightIssue","description":"Machine-readable preflight blocker or warning."},"OwnershipTransferPreflightResponse":{"properties":{"can_send":{"type":"boolean","title":"Can Send","description":"Whether the invitation can be sent (`true`) or a blocker stops it (`false`).","example":true},"recipient_status":{"type":"string","title":"Recipient Status","description":"Summary of the result. Either `can_send`, `recipient_must_sign_in_first` when the recipient doesn't have a Base44 account yet but the invitation can still be sent, `invalid_email`, or `blocked_policy` for any other blocker.","example":"can_send"},"blockers":{"items":{"$ref":"#/components/schemas/OwnershipTransferPreflightIssue"},"type":"array","title":"Blockers","description":"Problems that stop the invitation from being sent. It's empty when `can_send` is `true`."},"warnings":{"items":{"$ref":"#/components/schemas/OwnershipTransferPreflightIssue"},"type":"array","title":"Warnings","description":"Things to know that don't stop the invitation."},"transfer_package":{"anyOf":[{"$ref":"#/components/schemas/OwnershipTransferPackageSummary"},{"type":"null"}],"description":"What would transfer with the app, or `null` when you can't manage ownership transfers for this app."}},"type":"object","required":["can_send","recipient_status"],"title":"OwnershipTransferPreflightResponse","description":"Whether an ownership transfer invitation can be sent, and what would transfer with the app."},"OwnershipTransferSecretSummary":{"properties":{"secret_name":{"type":"string","title":"Secret Name","description":"Name of the app secret. Send it as `secret_name` in `secret_decisions`.","example":"STRIPE_API_KEY"},"has_value":{"type":"boolean","title":"Has Value","description":"Whether the secret has a value that can transfer with the app (`true`) or has none (`false`).","default":true,"example":true}},"type":"object","required":["secret_name"],"title":"OwnershipTransferSecretSummary","description":"Safe sender-facing secret summary. Values are never included."},"ParentHidden":{"properties":{"hidden_by":{"type":"string","title":"Hidden By","description":"The hidden parent page, by its name when the app has a page by that name, such as `Units`, or else by its URL, such as `/units`.","example":"Units"},"ignored":{"type":"boolean","title":"Ignored","description":"Whether the page opts out with `ignore_parent_noindex`, so the parent doesn't hide it. `false` means search engines don't see the page.","example":false}},"type":"object","required":["hidden_by","ignored"],"title":"ParentHidden","description":"How a hidden parent page affects one page."},"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."},"PauseTestRunResult":{"properties":{"success":{"type":"boolean","title":"Success","description":"Always `true`.","example":true},"already_terminal":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Already Terminal","description":"`true` when nothing changed: the run had already ended, or it was `analyzing`. Left out when the run was paused.","example":true}},"type":"object","required":["success"],"title":"PauseTestRunResult","description":"The result of pausing a run."},"PayNowResult":{"properties":{"status":{"type":"string","title":"Status","description":"What happened. `paid` means the card was charged and the balance is settled. `pending_payment` means a charge was already in flight, so nothing new was attempted. `already_settled` means the invoice turned out to be paid and the account is back to normal, and `paid_recovery_incomplete` means it is paid but the account is still held. Anything else is the `state` from the summary, meaning no charge was attempted: `no_debt`, `not_recoverable`, `requires_payment_method`, `requires_payment_refresh`, or `unavailable`. Read this rather than the HTTP status, which is 200 for every one of them.","example":"paid"},"summary":{"$ref":"#/components/schemas/WorkspaceDebtSummary","description":"The balance as it stands after the attempt, in the same shape [Get Google Ads workspace debt](/api-reference/get-google-ads-workspace-debt) returns. Read it back to confirm what actually changed."}},"type":"object","required":["status","summary"],"title":"PayNowResult","description":"The outcome of settling the workspace's ad-spend balance."},"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."},"PaymentSetupSession":{"properties":{"checkout_url":{"type":"string","title":"Checkout Url","description":"Stripe-hosted page where a person enters their card details. Hand this to a human rather than trying to complete it programmatically. It expires, so fetch it when someone is ready to use it.","example":"https://checkout.stripe.com/c/pay/cs_live_a1B2c3D4e5"},"session_id":{"type":"string","title":"Session Id","description":"Stripe's ID for the session. Useful for correlating with your own logs; the API takes it nowhere.","example":"cs_live_a1B2c3D4e5"}},"type":"object","required":["checkout_url","session_id"],"title":"PaymentSetupSession","description":"A Stripe Checkout session for saving a card."},"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."},"PaymentTransaction":{"properties":{"timestamp":{"type":"string","title":"Timestamp","description":"Transaction time as a UTC timestamp in ISO 8601 format.","example":"2026-08-25T10:00:00Z"},"action":{"type":"string","title":"Action","description":"What this entry represents. Either `payment`, `refund`, or `dispute_lost`.","example":"payment"},"status":{"type":"string","title":"Status","description":"Display status derived from the action. `payment` becomes `succeeded`, `refund` becomes `refunded`, and `dispute_lost` becomes `failed`. Other actions retain their action value. Defaults to an empty string.","default":"","example":"succeeded"},"amount":{"type":"integer","title":"Amount","description":"Amount in the currency's smallest unit. Read `action` to tell an incoming payment from an outgoing refund or dispute loss. Defaults to `0`.","default":0,"example":2500},"currency":{"type":"string","title":"Currency","description":"Currency code recorded with the transaction. Defaults to `usd`.","default":"usd","example":"usd"},"customer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Customer Id","description":"Provider customer ID, or `null` when none was recorded. Defaults to `null`.","example":"cus_T7mK2p9Q4r6S8v"},"transaction_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transaction Id","description":"Provider transaction ID, or `null` when none was recorded. Defaults to `null`.","example":"pi_T7mK2p9Q4r6S8v"},"customer_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Customer Name","description":"Customer name when provider enrichment succeeds, or `null` when unavailable. Defaults to `null`.","example":"Jane Doe"},"customer_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Customer Email","description":"Customer email when provider enrichment succeeds, or `null` when unavailable. Defaults to `null`.","example":"jane@example.com"},"is_live":{"type":"boolean","title":"Is Live","description":"Whether this entry was recorded in live mode. Defaults to `true`.","default":true,"example":true}},"type":"object","required":["timestamp","action"],"title":"PaymentTransaction","description":"A payment, refund, or lost dispute with optional customer details."},"PaymentTransactionsResponse":{"properties":{"transactions":{"items":{"$ref":"#/components/schemas/PaymentTransaction"},"type":"array","title":"Transactions","description":"Transactions on this page, newest first. Empty when none match or `offset` is past the end.","example":[{"action":"payment","amount":2500,"currency":"usd","customer_email":"jane@example.com","customer_id":"cus_T7mK2p9Q4r6S8v","customer_name":"Jane Doe","is_live":true,"status":"succeeded","timestamp":"2026-08-25T10:00:00Z","transaction_id":"pi_T7mK2p9Q4r6S8v"}]},"total":{"type":"integer","title":"Total","description":"Total number of matching transactions.","default":0,"example":347},"has_more":{"type":"boolean","title":"Has More","description":"Whether there are more items to fetch.","default":false,"example":true}},"type":"object","required":["transactions"],"title":"PaymentTransactionsResponse","description":"One page of an app's payment transactions."},"PaymentWebhookAck":{"properties":{"status":{"type":"string","title":"Status","description":"How Base44 handled the event. `processed` means it updated app state or recorded revenue, `acknowledged` means the event was valid but needed no action, and `ignored` means it was skipped. The Stripe endpoints also return `logged`, for an event recorded for reporting only.","example":"processed"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Why the event needed no action. Present when `status` is `ignored`, and on the `acknowledged` responses that skip an event, such as a transaction that was already approved or one for a zero amount.","example":"unknown_event_type"},"action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action","description":"What Base44 recorded. Present on most `processed` responses.","example":"payment_tracked"}},"type":"object","required":["status"],"title":"PaymentWebhookAck","description":"What Base44 did with an inbound payment webhook event."},"PayoutDetail":{"properties":{"payout":{"$ref":"#/components/schemas/PayoutItem","description":"The payout."},"records":{"items":{"$ref":"#/components/schemas/PayoutRecord"},"type":"array","title":"Records","description":"One page of the balance records the payout pays out, newest first.","example":[{"date":"2026-07-15","fee":30,"id":"9e2b7d41-6a3c-4f18-8d05-1c7e4a9b3f62","money":{"amount":1000,"currency":"usd"},"net":970,"payment_method":"creditCard","transaction_id":"2b8f4c61-7d93-4e05-a1c8-5f6e3d2b9a47","transaction_timestamp":"2026-07-14T10:00:00Z","type":"credit"}]},"summary_items":{"items":{"$ref":"#/components/schemas/PayoutSummaryItem"},"type":"array","title":"Summary Items","description":"Totals for the whole payout, one entry per record type. Only on the first page, and empty on later ones.","example":[{"amount":{"amount":22500,"currency":"usd"},"fees":{"amount":1015,"currency":"usd"},"net":{"amount":21485,"currency":"usd"},"type":"credits"}]},"records_total":{"type":"integer","title":"Records Total","description":"Total number of the payout's balance records created before the first page was read.","example":51},"has_more":{"type":"boolean","title":"Has More","description":"Whether more records follow this page.","example":true},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass as `cursor` to get the next page of records, or `null` on the last page. Expires after 24 hours.","example":"gAAAAABn7vJ2kXq9..."}},"type":"object","required":["payout","records","summary_items","records_total","has_more","next_cursor"],"title":"PayoutDetail"},"PayoutItem":{"properties":{"id":{"type":"string","title":"Id","description":"Wix Payments ID of the payout. Pass it to [Get Wix Payments payout](/api-reference/get-wix-payments-payout).","example":"7c1a3e52-4b0d-4f7e-9a61-2d8f5c3b9e10"},"date":{"type":"string","title":"Date","description":"Date the payout was created, as `YYYY-MM-DD`.","example":"2026-07-15"},"estimated_arrival":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Estimated Arrival","description":"Date the funds are expected to reach the bank account, as `YYYY-MM-DD`, or `null` when Wix doesn't give one.","example":"2026-07-18"},"status":{"type":"string","title":"Status","description":"Wix Payments payout status, lowercased, for example `sent` or `failed`. Wix can add values, so handle ones you don't recognize. `unknown` when Wix returns no status.","example":"sent"},"money":{"$ref":"#/components/schemas/PayoutMoney","description":"Amount of the payout."},"account_profile_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Profile Id","description":"ID of the Wix Payments account profile the payout belongs to, or `null` when Wix doesn't return one.","example":"3f6d2a90-8e14-4c57-b2a9-6e0f1d7c4b28"},"bank_transfer_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bank Transfer Reference","description":"Reference the bank shows for the transfer, or `null` when there isn't one yet.","example":"WIX-PO-20260715-0042"},"failure_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Failure Reason","description":"Wix's code for why the payout failed, or `null` when it didn't fail."}},"type":"object","required":["id","date","estimated_arrival","status","money","account_profile_id"],"title":"PayoutItem"},"PayoutList":{"properties":{"payouts":{"items":{"$ref":"#/components/schemas/PayoutItem"},"type":"array","title":"Payouts","description":"One page of payouts, newest first.","example":[{"account_profile_id":"3f6d2a90-8e14-4c57-b2a9-6e0f1d7c4b28","date":"2026-07-15","estimated_arrival":"2026-07-18","id":"7c1a3e52-4b0d-4f7e-9a61-2d8f5c3b9e10","money":{"amount":21485,"currency":"usd"},"status":"sent"}]},"scheduled":{"anyOf":[{"$ref":"#/components/schemas/ScheduledPayout"},{"type":"null"}],"description":"Funds already scheduled to be paid out. `null` when nothing is scheduled, when the balance couldn't be read, and on every page after the first."},"on_hold":{"anyOf":[{"$ref":"#/components/schemas/PayoutMoney"},{"type":"null"}],"description":"Funds Wix is holding back from payouts. `null` when nothing is on hold, when the balance couldn't be read, and on every page after the first."},"balance_unavailable":{"type":"boolean","title":"Balance Unavailable","description":"`true` when Wix couldn't return the balance, so `scheduled` and `on_hold` are `null` even if funds exist. Always `false` on pages after the first, which don't read the balance.","example":false},"total":{"type":"integer","title":"Total","description":"Total number of payouts created before the first page was read, as Wix reports it.","example":21},"has_more":{"type":"boolean","title":"Has More","description":"Whether more payouts follow this page.","example":true},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass as `cursor` to get the next page, or `null` on the last page. Expires after 24 hours.","example":"gAAAAABn7vJ2kXq9..."}},"type":"object","required":["payouts","scheduled","on_hold","balance_unavailable","total","has_more","next_cursor"],"title":"PayoutList"},"PayoutMoney":{"properties":{"amount":{"type":"integer","title":"Amount","description":"Amount in the currency's smallest unit, such as cents, so `21485` is 214.85 in USD.","example":21485},"currency":{"type":"string","title":"Currency","description":"Lowercase three-letter ISO 4217 currency code.","example":"usd"}},"type":"object","required":["amount","currency"],"title":"PayoutMoney"},"PayoutRecord":{"properties":{"id":{"type":"string","title":"Id","description":"Wix Payments ID of the balance record.","example":"9e2b7d41-6a3c-4f18-8d05-1c7e4a9b3f62"},"type":{"type":"string","title":"Type","description":"Wix balance record type, lowercased, for example `credit`. `unknown` when Wix returns none.","example":"credit"},"date":{"type":"string","title":"Date","description":"Date the record was applied to the balance, as `YYYY-MM-DD`.","example":"2026-07-15"},"money":{"$ref":"#/components/schemas/PayoutMoney","description":"Gross amount of the record, before fees."},"fee":{"type":"integer","title":"Fee","description":"Fee Wix took on the record, in the smallest unit of `money.currency`.","example":30},"net":{"type":"integer","title":"Net","description":"Amount left after the fee, in the smallest unit of `money.currency`.","example":970},"transaction_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transaction Id","description":"Wix Payments ID of the transaction behind the record, or `null` when the record has no transaction behind it.","example":"2b8f4c61-7d93-4e05-a1c8-5f6e3d2b9a47"},"transaction_timestamp":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transaction Timestamp","description":"When the transaction behind the record was created, as an ISO 8601 timestamp, or `null` when there's no transaction.","example":"2026-07-14T10:00:00Z"},"payment_method":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payment Method","description":"Wix's ID for the payment method the customer used, for example `creditCard`, or `null` when Wix doesn't return one.","example":"creditCard"}},"type":"object","required":["id","type","date","money","fee","net","transaction_id","transaction_timestamp","payment_method"],"title":"PayoutRecord"},"PayoutSummaryItem":{"properties":{"type":{"type":"string","title":"Type","description":"Wix summary type, lowercased, for example `credits`. `unknown` when Wix returns none.","example":"credits"},"amount":{"$ref":"#/components/schemas/PayoutMoney","description":"Gross total for this type."},"fees":{"$ref":"#/components/schemas/PayoutMoney","description":"Fees for this type."},"net":{"$ref":"#/components/schemas/PayoutMoney","description":"Amount left after fees for this type."}},"type":"object","required":["type","amount","fees","net"],"title":"PayoutSummaryItem"},"PaywallStatusContext":{"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":"PaywallStatusContext"},"PendingOwnershipTransfer":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the ownership transfer.","example":"68d4b1e9c2a7f3001e5b8c40"},"app_id":{"type":"string","title":"App Id","description":"ID of the app being transferred.","example":"6820f3a4e7b91d003c45a1f2"},"new_owner_email":{"type":"string","title":"New Owner Email","description":"Email address the transfer invitation was sent to.","example":"jane@acme.com"},"status":{"type":"string","title":"Status","description":"Either `pending`, while the invitation waits for the recipient, or `accepting`, while the recipient's acceptance is being applied.","example":"pending"},"expires_at":{"type":"string","title":"Expires At","description":"When the invitation expires, as a UTC timestamp in ISO 8601 format. The recipient can't accept it after that.","example":"2026-10-05T09:23:41.512000Z"}},"type":"object","required":["id","app_id","new_owner_email","status","expires_at"],"title":"PendingOwnershipTransfer","description":"An ownership transfer waiting for its recipient to accept it."},"PendingOwnershipTransferResponse":{"properties":{"transfer":{"anyOf":[{"$ref":"#/components/schemas/PendingOwnershipTransfer"},{"type":"null"}],"description":"The pending transfer, or `null` when the app has none.","example":{"app_id":"6820f3a4e7b91d003c45a1f2","expires_at":"2026-10-05T09:23:41.512000Z","id":"68d4b1e9c2a7f3001e5b8c40","new_owner_email":"jane@acme.com","status":"pending"}}},"type":"object","required":["transfer"],"title":"PendingOwnershipTransferResponse","description":"The app's pending ownership transfer, if it has one."},"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."},"PreflightTransferRequest":{"properties":{"new_owner_email":{"type":"string","title":"New Owner Email","description":"Email address of the new owner. An invalid address comes back as a blocker.","example":"jane@acme.com"},"destination":{"anyOf":[{"type":"string","enum":["same_workspace","external_email"]},{"type":"null"}],"title":"Destination","description":"Either `same_workspace`, to invite an owner, admin, editor, or member of the app's workspace, or `external_email`, to invite anyone. Defaults to `external_email`.","example":"external_email"}},"type":"object","required":["new_owner_email"],"title":"PreflightTransferRequest","description":"The ownership transfer invitation to check."},"PreviewUrlResponse":{"properties":{"preview_url":{"type":"string","title":"Preview Url","description":"Where the app is served as it currently stands in the app editor, including changes that haven't been published. Usually a host with no scheme, so add `https://` before you open it in a browser or an iframe. For some imported apps it's already a full URL with its own query parameters, so add the scheme only when it's missing and keep the query.","example":"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. It works once and expires after 5 minutes. Add it to the preview's query as `_preview_token`. Treat it as a credential and don't share it, because anyone holding it can load the app before it is published.","example":"FH-j7wHS7IR_fC1tUh4wCd_fNV1XGC479cuqNSYi9mA"},"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."},"PreviousRepository":{"properties":{"repo_full_name":{"type":"string","title":"Repo Full Name","description":"Full repository name, as `owner/repo`.","example":"base44/lead-tracker"},"repo_url":{"type":"string","title":"Repo Url","description":"URL of the repository on GitHub.","example":"https://github.com/base44/lead-tracker"},"org_name":{"type":"string","title":"Org Name","description":"GitHub username or organization that owns the repository.","example":"base44"}},"type":"object","required":["repo_full_name","repo_url","org_name"],"title":"PreviousRepository","description":"The repository an app was connected to before a user disconnected 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."},"ProvisionLiveKeysResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether live key provisioning completed.","example":true},"message":{"type":"string","title":"Message","description":"Human-readable provisioning result.","example":"Live API keys provisioned successfully."},"already_configured":{"type":"boolean","title":"Already Configured","description":"Whether provisioning was already complete or another concurrent call completed it. Defaults to `false`.","default":false,"example":false}},"type":"object","required":["success","message"],"title":"ProvisionLiveKeysResponse","description":"Response for automatic live key provisioning."},"ProvisionUserPayload":{"properties":{"email":{"type":"string","format":"email","title":"Email","description":"The app user's email — their identity in this app"},"role":{"type":"string","title":"Role","description":"Application-level role for in-app permissions"},"full_name":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Full Name"}},"additionalProperties":false,"type":"object","required":["email","role"],"title":"ProvisionUserPayload","description":"Payload for server-to-server app-user provisioning (embedded platforms)."},"ProvisionUserResponse":{"properties":{"status":{"type":"string","enum":["created","exists"],"title":"Status","description":"`created` for a new email, `exists` for one that already had an access request.","example":"created"},"email":{"type":"string","title":"Email","description":"The email, lowercased.","example":"dana@example.com"},"role":{"type":"string","title":"Role","description":"The role the email holds in the app. For `exists`, the role it already had.","example":"user"}},"type":"object","required":["status","email","role"],"title":"ProvisionUserResponse","description":"The app user an email was provisioned as."},"PublicPaginationInfo":{"properties":{"total":{"type":"integer","title":"Total","description":"Total number of matching items.","example":347},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Cursor for fetching the next page. The value is `null` if there are no more pages.","example":"gAAAAABn7vJ..."},"has_more":{"type":"boolean","title":"Has More","description":"Whether there are more items to fetch.","example":true}},"type":"object","required":["total","has_more"],"title":"PublicPaginationInfo","description":"Pagination metadata for a cursor-paginated page."},"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."},"QueuedMessage":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the queued message.","example":"3b9d2f6e-8c41-4a7e-9f05-1d2c3b4a5e6f"},"content":{"type":"string","title":"Content","description":"Text of the message, as it will be sent to the AI.","example":"Add a contact form to the home page"},"branch_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branch Id","description":"ID of the branch the message was queued on, or `null` for main. The API acts on main's queue, so this is normally `null`.","example":"68f1a2b3c4d5e6f708192a3b"},"created_at":{"type":"string","title":"Created At","description":"When the message was queued, as a UTC timestamp in ISO 8601 format.","example":"2026-10-05T14:30:00.123456+00:00"}},"type":"object","required":["id","content","branch_id","created_at"],"title":"QueuedMessage","description":"A message waiting for the AI to finish its current turn."},"ReactionResult":{"properties":{"emoji":{"type":"string","title":"Emoji","description":"The emoji you toggled.","example":"👍"},"user_ids":{"items":{"type":"string"},"type":"array","title":"User Ids","description":"IDs of everyone who now reacts with this emoji on the comment. Your ID is missing when you removed your reaction.","example":["6820f41be7b91d003c45a20a"]}},"type":"object","required":["emoji","user_ids"],"title":"ReactionResult"},"ReadBatch":{"properties":{"paths":{"items":{"type":"string"},"type":"array","maxItems":32,"minItems":1,"title":"Paths","description":"Paths of up to 32 files, relative to the app root.","example":["src/App.jsx","src/pages/Home.jsx"]},"max_bytes":{"type":"integer","maximum":5242880.0,"exclusiveMinimum":0.0,"title":"Max Bytes","description":"Most bytes of file text to return across all the files, up to 5242880 (5 MB).","default":5242880,"example":1048576}},"type":"object","required":["paths"],"title":"ReadBatch","description":"The files to read."},"ReadFileEntry":{"properties":{"path":{"type":"string","title":"Path","description":"The path you asked for, echoed back so you can match entries to your request. The only field present on every entry.","example":"src/pages/Home.jsx"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content","description":"The file's text over the returned line range. Absent when this path failed.","example":"export default function Home() {\n  return <h1>Hello</h1>;\n}\n"},"start_line":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Start Line","description":"First line included, 1-based. Absent when this path failed.","example":1},"end_line":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"End Line","description":"Last line included, 1-based. Absent when this path failed.","example":3},"total_lines":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Lines","description":"How many lines the whole file has, so you can tell whether you received all of it. Absent when this path failed.","example":3},"truncated":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Truncated","description":"`true` when this entry was cut short, either by your `limit` or by the 1 MB per-file cap. Absent when this path failed.","example":false},"error":{"anyOf":[{"$ref":"#/components/schemas/ReadFileError"},{"type":"null"}],"description":"Why this path could not be read, and absent when it could. Its presence is what marks a failed entry."}},"type":"object","required":["path"],"title":"ReadFileEntry","description":"One path from a read request: either its contents or why it failed.\n\nEvery field except ``path`` is absent-able, because an entry is one of two\nshapes. A successful read carries the five content fields and no ``error``;\na failed one carries ``error`` and nothing else."},"ReadFileError":{"properties":{"code":{"type":"string","title":"Code","description":"Stable machine-readable reason, from the same taxonomy the error responses use: `NOT_FOUND`, `BINARY_FILE`, `PATH_OUTSIDE_SANDBOX`, `PROTECTED_PATH`, `READ_BUDGET_EXCEEDED` or `BACKEND_ERROR`. Branch on this rather than on the message.","example":"NOT_FOUND"},"message":{"type":"string","title":"Message","description":"Human-readable explanation for this path.","example":"File not found."}},"type":"object","required":["code","message"],"title":"ReadFileError","description":"Why one path in the batch could not be read."},"ReadFileResult":{"properties":{"files":{"items":{"$ref":"#/components/schemas/ReadFileEntry"},"type":"array","title":"Files","description":"One entry per requested path, in request order. Each is either a successful read or an error for that path, so check for `error` before reading `content`.","example":[{"content":"export default function Home() {\n  return <h1>Hello</h1>;\n}\n","end_line":3,"path":"src/pages/Home.jsx","start_line":1,"total_lines":3,"truncated":false},{"error":{"code":"NOT_FOUND","message":"File not found."},"path":"src/pages/Missing.jsx"}]}},"type":"object","required":["files"],"title":"ReadFileResult","description":"The files you asked for, in the order you asked for them."},"ReadResult":{"properties":{"read_at":{"type":"string","format":"date-time","title":"Read At","description":"The read time recorded for you, in UTC.","example":"2026-10-05T09:14:22.512000Z"}},"type":"object","required":["read_at"],"title":"ReadResult"},"RecentTransactionResponse":{"properties":{"timestamp":{"type":"string","title":"Timestamp","description":"Transaction time in UTC as returned by the analytics store. The string can omit an explicit timezone offset.","example":"2026-08-25 10:00:00"},"action":{"type":"string","title":"Action","description":"What this entry represents. Either `payment`, `refund`, or `dispute_lost`.","example":"payment"},"status":{"type":"string","title":"Status","description":"Display status derived from the action. `payment` becomes `succeeded`, `refund` becomes `refunded`, and `dispute_lost` becomes `failed`. Other actions retain their action value. Defaults to an empty string.","default":"","example":"succeeded"},"amount":{"type":"integer","title":"Amount","description":"Amount in the currency's smallest unit. Read `action` to tell an incoming payment from an outgoing refund or dispute loss. Defaults to `0`.","default":0,"example":2500},"currency":{"type":"string","title":"Currency","description":"Currency code recorded with the transaction. Defaults to `usd`.","default":"usd","example":"usd"},"customer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Customer Id","description":"Provider customer ID, or `null` when none was recorded. Defaults to `null`.","example":"cus_T7mK2p9Q4r6S8v"},"transaction_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transaction Id","description":"Provider transaction ID, or `null` when none was recorded. Defaults to `null`.","example":"pi_T7mK2p9Q4r6S8v"},"customer_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Customer Name","description":"Customer name when provider enrichment succeeds, or `null` when unavailable. Defaults to `null`.","example":"Jane Doe"},"customer_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Customer Email","description":"Customer email when provider enrichment succeeds, or `null` when unavailable. Defaults to `null`.","example":"jane@example.com"},"is_live":{"type":"boolean","title":"Is Live","description":"Whether this entry was recorded in live mode. Defaults to `true`.","default":true,"example":true}},"type":"object","required":["timestamp","action"],"title":"RecentTransactionResponse","description":"A recent transaction with optional customer details."},"RecentTransactionsResponse":{"properties":{"transactions":{"items":{"$ref":"#/components/schemas/RecentTransactionResponse"},"type":"array","title":"Transactions","description":"Recent payments, refunds, and disputes, newest first. Empty when none match.","example":[{"action":"payment","amount":2500,"currency":"usd","customer_email":"jane@example.com","customer_id":"cus_T7mK2p9Q4r6S8v","customer_name":"Jane Doe","is_live":true,"status":"succeeded","timestamp":"2026-08-25 10:00:00","transaction_id":"pi_T7mK2p9Q4r6S8v"}]}},"type":"object","required":["transactions"],"title":"RecentTransactionsResponse","description":"Response with recent transactions list."},"RecordingCaptureSettingsUpdate":{"properties":{"exclude_admins":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Exclude Admins","description":"`true` to leave visits by the app's owners and admins out of recording, `false` to record them too.","example":false},"sampling_rate":{"anyOf":[{"type":"number","multipleOf":0.05,"maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Sampling Rate","description":"Share of eligible visits to record, from `0` to `1` in steps of `0.05`. `1` records every visit.","example":0.5}},"additionalProperties":false,"type":"object","title":"RecordingCaptureSettingsUpdate"},"RecordingSettingsResponse":{"properties":{"app_id":{"type":"string","title":"App Id","description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"},"enabled":{"type":"boolean","title":"Enabled","description":"Whether recordings are on. Reads `false` when they were turned on but the app has since moved to another owner or workspace, or the owner's plan no longer includes recordings.","example":true},"enabled_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enabled At","description":"When recordings were turned on, as a UTC timestamp in ISO 8601 format, or `null` when they're off. A value read back carries no offset, and the response right after turning recordings on carries `+00:00`. The response to turning recordings off keeps the previous value.","example":"2026-09-01T10:15:00.123000"},"enabled_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enabled By","description":"Email of the user who turned recordings on, or `null` when they're off. The response to turning recordings off keeps the previous value.","example":"jane@acme.com"},"exclude_admins":{"type":"boolean","title":"Exclude Admins","description":"Whether visits by the app's owners and admins are left out of recording.","default":true,"example":true},"sampling_rate":{"type":"number","title":"Sampling Rate","description":"Share of eligible visits that get recorded, from `0` to `1`. `1` records every visit.","default":1.0,"example":1.0}},"type":"object","required":["app_id","enabled"],"title":"RecordingSettingsResponse","description":"An app's session recording settings."},"RecordingSettingsUpdate":{"properties":{"enabled":{"type":"boolean","title":"Enabled","description":"`true` to turn recordings on, `false` to turn them off.","example":true}},"type":"object","required":["enabled"],"title":"RecordingSettingsUpdate"},"RedirectURI":{"properties":{"host":{"type":"string","title":"Host","description":"Host the app serves on.","example":"acme-crm.base44.app"},"uri":{"type":"string","title":"Uri","description":"Redirect URI to register for this host.","example":"https://acme-crm.base44.app/api/external-auth/callback"},"is_custom_domain":{"type":"boolean","title":"Is Custom Domain","description":"Whether the host is one of the app's custom domains (`true`) or a Base44 host (`false`).","example":false}},"type":"object","required":["host","uri","is_custom_domain"],"title":"RedirectURI","description":"A host the app's OAuth callback can return to."},"RefineFlowRequest":{"properties":{"description":{"type":"string","maxLength":500,"minLength":1,"title":"Description","description":"Rough description of what to test, in your own words.","example":"check that new projects show up on the dashboard"}},"type":"object","required":["description"],"title":"RefineFlowRequest"},"RefinePostPayload":{"properties":{"scheduled_post_id":{"anyOf":[{"type":"string","pattern":"^[0-9a-fA-F]{24}$"},{"type":"null"}],"title":"Scheduled Post Id","description":"Optional calendar post ID from List scheduled posts. Its source_post_id must match the post_id in the URL. Updates the calendar row rather than the original plan; requires Social Calendar V2. Only proposal and scheduled posts can be edited.","example":"6820f3a4e7b91d003c45a1f2"},"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"},"RefinedTestDescription":{"properties":{"name":{"type":"string","title":"Name","description":"Short name for the test.","example":"Create a project"},"goal":{"type":"string","title":"Goal","description":"The goal, rewritten as steps the testing agent can follow.","example":"Sign up, create a project called Launch, and check that it appears on the dashboard."}},"type":"object","required":["name","goal"],"title":"RefinedTestDescription","description":"A test name and goal written from a rough description."},"RefundTransactionRequest":{"properties":{"amount":{"type":"integer","exclusiveMinimum":0.0,"title":"Amount","description":"Amount to refund, in the smallest unit of the payment's own currency, so `1000` is $10.00 for a USD payment. Must be above `0`.","example":1000},"note":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Note","description":"Private note stored with the refund, up to 500 characters. Defaults to `null`.","example":"Customer returned one item."},"idempotency_key":{"anyOf":[{"type":"string","maxLength":100,"minLength":1},{"type":"null"}],"title":"Idempotency Key","description":"Key you generate for this refund attempt, up to 100 characters. Keep it the same across retries of one attempt, and use a new one for each new refund. Required unless you send it in the `Idempotency-Key` header instead.","example":"refund-2026-08-25-7f3c9a"}},"additionalProperties":false,"type":"object","required":["amount"],"title":"RefundTransactionRequest","description":"A refund of all or part of one payment."},"RemixRequirements":{"properties":{"name":{"type":"string","title":"Name","description":"Display name of the app. The remix is named after it with ` (Copy)` appended, except for an approved template, where it takes the name of the approved version.","example":"My CRM"},"app_type":{"type":"string","title":"App Type","description":"Type of the app. Only `slide`, `user_agent`, `user_app`, `user_game` apps can be remixed.","example":"user_app"},"user_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Description","description":"Description of the app, or `null` if none was set.","example":"A CRM to track leads and deals"},"requires_backend_functions":{"type":"boolean","title":"Requires Backend Functions","description":"Whether the app has deployed backend functions, which the remix copies.","example":false},"app_secret_names":{"items":{"type":"string"},"type":"array","title":"App Secret Names","description":"Names of the secrets set on the app. The remix doesn't copy their values, except the ones in `shared_secrets`, so pass the rest in `secret_values`.","example":["STRIPE_API_KEY","OPENAI_API_KEY"]},"shared_secrets":{"items":{"type":"string"},"type":"array","title":"Shared Secrets","description":"Names of the secrets the remix copies automatically, values included. Only an approved workspace template shares secrets, and only with members who can edit in the workspace and aren't the app's owner, collaborators, or workspace admins. Empty otherwise.","example":["OPENAI_API_KEY"]},"is_admin":{"type":"boolean","title":"Is Admin","description":"Whether you are an editor-level collaborator on the app or an admin of its workspace. You can set `with_chat_history` when this is `true`, and also when you own the app.","example":false}},"type":"object","required":["name","app_type","user_description","requires_backend_functions","app_secret_names","shared_secrets","is_admin"],"title":"RemixRequirements","description":"What remixing an app involves, before you remix it."},"RemoveCollaboratorResult":{"properties":{"success":{"type":"boolean","title":"Success","description":"Always `true`.","example":true}},"type":"object","required":["success"],"title":"RemoveCollaboratorResult","description":"The result of removing a collaborator."},"RemoveFolderAppResult":{"properties":{"unlinked":{"type":"boolean","title":"Unlinked","description":"Always `true`, including when the app wasn't in the folder.","example":true},"folder_id":{"type":"string","title":"Folder Id","description":"ID of the folder.","example":"68c1f2a9b4e7d3005a2c9e11"},"app_id":{"type":"string","title":"App Id","description":"ID of the app, in lowercase.","example":"6820f3a4e7b91d003c45a1f2"}},"type":"object","required":["unlinked","folder_id","app_id"],"title":"RemoveFolderAppResult"},"RemoveIntegrationResult":{"properties":{"status":{"type":"string","const":"removed","title":"Status","description":"Always `removed`.","example":"removed"},"integration_type":{"type":"string","title":"Integration Type","description":"Connector that was removed.","example":"googlecalendar"}},"type":"object","required":["status","integration_type"],"title":"RemoveIntegrationResult","description":"Confirmation that the connector is removed from the app."},"ReopenedThread":{"properties":{"resolved_at":{"type":"null","title":"Resolved At","description":"Always `null`."}},"type":"object","required":["resolved_at"],"title":"ReopenedThread"},"ReorderQueuePayload":{"properties":{"item_ids":{"items":{"type":"string"},"type":"array","title":"Item Ids","description":"IDs of queued messages in the order you want them to run. IDs not in the queue are ignored, and queued messages you leave out keep their order after the ones you list. Each ID can appear once.","example":["3b9d2f6e-8c41-4a7e-9f05-1d2c3b4a5e6f","9a1c4e2b-6d3f-4b8a-a7e1-2f5c8d0b3e4a"]}},"type":"object","required":["item_ids"],"title":"ReorderQueuePayload"},"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."},"ReplyToAppCommentPayload":{"properties":{"content":{"type":"string","maxLength":5000,"minLength":1,"title":"Content","description":"Text of the comment, 1 to 5000 characters.","example":"Done, thanks!"},"mentioned_emails":{"items":{"type":"string"},"type":"array","maxItems":50,"title":"Mentioned Emails","description":"Emails of up to 50 people the comment mentions. Mentions send no notification.","example":["dana@acme.com"]}},"type":"object","required":["content"],"title":"ReplyToAppCommentPayload","description":"A reply to a comment thread."},"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."},"RequestDeployRequest":{"properties":{"requested_visibility":{"type":"string","enum":["private_with_login","workspace_with_login","public_with_login","public_without_login"],"title":"Requested Visibility","description":"Who you're asking to be able to open the published app. The values are the ones `public_settings` takes in [Update app](/api-reference/update-app).","example":"public_with_login"},"message":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Message","description":"A note to the approvers, up to 1000 characters. It's included in the notification and the email.","example":"Ready for the client demo on Friday."}},"type":"object","required":["requested_visibility"],"title":"RequestDeployRequest"},"ResendInviteRequest":{"properties":{"email":{"type":"string","maxLength":254,"minLength":5,"title":"Email","description":"Email the pending invitation was sent to.","example":"dana@acme.com"}},"type":"object","required":["email"],"title":"ResendInviteRequest","description":"A pending invitation to send again."},"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."},"ResolveIssueRequest":{"properties":{"title":{"type":"string","maxLength":400,"title":"Title","description":"The issue's `title`, as the report shows it. Case and punctuation don't matter.","example":"Project doesn't appear after creation"}},"type":"object","required":["title"],"title":"ResolveIssueRequest"},"ResolveNameResponse":{"properties":{"needs_full_name":{"type":"boolean","title":"Needs Full Name","description":"Whether onboarding still needs a first and last name.","example":false},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name","description":"Resolved first name, or `null` when a complete name could not be found. Defaults to `null`.","example":"Jane"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name","description":"Resolved last name, or `null` when a complete name could not be found. Defaults to `null`.","example":"Doe"}},"type":"object","required":["needs_full_name"],"title":"ResolveNameResponse"},"ResolveOutcome":{"type":"string","enum":["resolved","needs_input","apply_pending","conflict","failed"],"title":"ResolveOutcome","description":"Final status of a conflict-resolution run."},"ResolvedThread":{"properties":{"resolved_at":{"type":"string","format":"date-time","title":"Resolved At","description":"When the thread was resolved, in UTC.","example":"2026-10-05T09:14:22.512000Z"}},"type":"object","required":["resolved_at"],"title":"ResolvedThread"},"RestoreCounts":{"properties":{"reverted":{"type":"integer","title":"Reverted","description":"Records put back to the values they had at the checkpoint.","default":0,"example":12},"deleted":{"type":"integer","title":"Deleted","description":"Records created after the checkpoint, moved to the entity's trash.","default":0,"example":3},"reinserted":{"type":"integer","title":"Reinserted","description":"Records deleted after the checkpoint, brought back.","default":0,"example":1}},"type":"object","title":"RestoreCounts","description":"How many records a restore changed in one entity."},"RestoreResult":{"properties":{"app_id":{"type":"string","title":"App Id","description":"ID of the restored app.","example":"6820f3a4e7b91d003c45a1f2"},"entities_undeleted":{"type":"integer","title":"Entities Undeleted","description":"Number of entity records brought back with the app.","default":0,"example":128},"new_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"New Slug","description":"The app's new slug when another app took its old one while it was in the trash, or `null` when the app kept its slug.","example":"acme-crm-6820f3a4"},"functions_redeployed":{"type":"boolean","title":"Functions Redeployed","description":"Whether the app's backend functions were deployed again. When `false`, `reconnect_needed` includes `functions`. `true` for an app with no functions.","default":true,"example":true},"reconnect_needed":{"items":{"type":"string"},"type":"array","title":"Reconnect Needed","description":"What you still need to do. It has the `deleted_resources` values from [Delete app](/api-reference/delete-app), which the restore doesn't rebuild, plus `functions` when the backend functions didn't deploy, `republish` when the published app needs a new [deploy](/api-reference/deploy-an-app) to run its functions, and `runtime` when the published app isn't reachable yet. Base44 retries that last one in the background.","default":[],"example":["domains"]}},"type":"object","required":["app_id"],"title":"RestoreResult"},"RetryCheckpointBuildResponse":{"properties":{"status":{"type":"string","enum":["building","ready","skipped"],"title":"Status","description":"Where the build stands when the call returns. Either `building` (the build started in the background), `ready` (the checkpoint's commit was already built, so nothing was rebuilt), or `skipped` (the app was imported from an external repository, and those apps have no preview build).","example":"building"},"git_commit_hash":{"type":"string","title":"Git Commit Hash","description":"Git commit hash of the code being built, the same as the checkpoint's `git_commit_hash`.","example":"a1b2c3d4e5f67890abcdef1234567890abcdef12"}},"type":"object","required":["status","git_commit_hash"],"title":"RetryCheckpointBuildResponse","description":"Where a checkpoint's preview build stands when the call returns."},"ReviewAccessRequestResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Always `true`. A review that doesn't happen returns an error instead.","example":true},"message":{"type":"string","title":"Message","description":"`Access request approved` or `Access request denied`.","example":"Access request approved"},"access_request":{"$ref":"#/components/schemas/ReviewedAccessRequest"}},"type":"object","required":["success","message","access_request"],"title":"ReviewAccessRequestResponse","description":"Doc-only: the handler returns a plain dict with exactly these keys."},"ReviewedAccessRequest":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the access request.","example":"68d4a1f7c2b9e5001a7f3c60"},"app_id":{"type":"string","title":"App Id","description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"},"email":{"type":"string","title":"Email","description":"Email of the person.","example":"jane@acme.com"},"status":{"type":"string","title":"Status","description":"`completed` when the person was added to the app, `approved` when they join once they sign up, or `denied`.","example":"completed"}},"type":"object","required":["id","app_id","email","status"],"title":"ReviewedAccessRequest"},"RlsFixFailure":{"properties":{"entity_name":{"type":"string","title":"Entity Name","description":"Entity that wasn't fixed.","example":"Customer"},"reason":{"type":"string","enum":["no_scan","no_recommendation","entity_not_found","write_failed"],"title":"Reason","description":"Why it wasn't fixed. `no_scan` means the app has no scan result from the current scanner, so run [Run security scan](/api-reference/run-security-scan) first. `no_recommendation` means the latest result holds no recommendation for the entity, because it never had one or it was already resolved. `entity_not_found` means the app no longer has the entity. `write_failed` means saving the rules failed, though some of them may have landed, and the recommendation is kept so you can retry.","example":"no_recommendation"}},"type":"object","required":["entity_name","reason"],"title":"RlsFixFailure","description":"One entity whose recommended rules weren't applied."},"RlsFixRequest":{"properties":{"entity_names":{"items":{"type":"string"},"type":"array","maxItems":100,"minItems":1,"title":"Entity Names","description":"Entities to fix, as returned in `rls_recommendations` by [Get security scan](/api-reference/get-security-scan). Name at least one and at most 100. A name listed twice is applied once.","example":["Order","Customer"]},"record_security_fix_chat":{"type":"boolean","title":"Record Security Fix Chat","description":"Whether to add the fix to the app's AI chat and save a checkpoint of the app first (`true`) or apply it with neither (`false`). Defaults to `true`.","default":true,"example":true},"security_fix_total_count":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Security Fix Total Count","description":"When you split one fix across several calls, the total number of entities across all of them. It's used only in the chat message, and ignored unless it's larger than the number of names in `entity_names`.","example":150}},"additionalProperties":false,"type":"object","required":["entity_names"],"title":"RlsFixRequest","description":"Which entities to apply the recommended row-level security rules to."},"RlsFixResponse":{"properties":{"fixed":{"items":{"type":"string"},"type":"array","title":"Fixed","description":"Entities whose `rls` now holds the recommended rules. An entity can also appear in `failed` as `no_recommendation` when a newer scan replaced its recommendation while the rules were being applied.","example":["Order"]},"failed":{"items":{"$ref":"#/components/schemas/RlsFixFailure"},"type":"array","title":"Failed","description":"Entities that weren't fixed, each with the reason.","example":[{"entity_name":"Customer","reason":"no_recommendation"}]}},"type":"object","required":["fixed","failed"],"title":"RlsFixResponse","description":"Which entities got their recommended row-level security rules."},"RunCommandResult":{"properties":{"stdout":{"type":"string","title":"Stdout","description":"Everything the command wrote to standard output.","example":"added 12 packages in 3s\n"},"stderr":{"type":"string","title":"Stderr","description":"Everything the command wrote to standard error. Populated on success too, since many tools log there.","example":""},"exit_code":{"type":"integer","title":"Exit Code","description":"The command's exit status. `0` means success. A non-zero status is still a 200 from this endpoint: the command ran and failed, which is not a request error.","example":0},"truncated":{"type":"boolean","title":"Truncated","description":"`true` when the output hit the 1 MB cap and was cut short. `stdout` and `stderr` are each capped, and either one hitting it sets this.","example":false},"duration_ms":{"type":"integer","title":"Duration Ms","description":"How long the command took, in milliseconds.","example":3120}},"type":"object","required":["stdout","stderr","exit_code","truncated","duration_ms"],"title":"RunCommandResult","description":"What a command left behind."},"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: `scheduled`, `entity`, `connector`, `in_app_agent`, `app_user_auth`, `app_publish`, `app_payment`, `webhook`, or `goal_file`. See [Triggers](/developers/references/apps-api/sections/workflows#triggers) for what each one fires on. A run started through [Run a workflow now](/api-reference/run-a-workflow-now) with no payload to replay reports `manual` instead.","default":"","example":"scheduled"},"status":{"type":"string","title":"Status","description":"How the run is going: `running`, `completed`, `failed`, or `cancelled`. See [Workflow status](/developers/references/apps-api/sections/workflows#workflow-status) for how this compares to the workflow's own status.","example":"completed"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At","description":"When the run started, as an ISO 8601 UTC timestamp.","example":"2026-08-25T09:12:44Z"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At","description":"When the run finished, as an ISO 8601 UTC timestamp. This is `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. This is `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":"This is `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. This is `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"},"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: `scheduled`, `entity`, `connector`, `in_app_agent`, `app_user_auth`, `app_publish`, `app_payment`, `webhook`, or `goal_file`. See [Triggers](/developers/references/apps-api/sections/workflows#triggers) for what each one fires on. A run started through [Run a workflow now](/api-reference/run-a-workflow-now) with no payload to replay reports `manual` instead.","default":"","example":"scheduled"},"status":{"type":"string","title":"Status","description":"How the run is going: `running`, `completed`, `failed`, or `cancelled`. See [Workflow status](/developers/references/apps-api/sections/workflows#workflow-status) for how this compares to the workflow's own status.","example":"completed"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At","description":"When the run started, as an ISO 8601 UTC timestamp.","example":"2026-08-25T09:12:44Z"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At","description":"When the run finished, as an ISO 8601 UTC timestamp. This is `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. This is `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. This is `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":"This is `true` when the run was started by hand through [Run a workflow now](/api-reference/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"},"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"},"SEOCategoryScore":{"properties":{"score":{"type":"integer","title":"Score","description":"Score for this category, 0 to 100. A passing check counts 100, a warning 50 and a failure 0, averaged over the category's checks.","example":80},"passed":{"type":"integer","title":"Passed","description":"Checks in this category that passed. A plain count, so the three counts add up to the category's checks in `checks`.","default":0,"example":4},"warnings":{"type":"integer","title":"Warnings","description":"Checks in this category that returned a warning.","default":0,"example":1},"failures":{"type":"integer","title":"Failures","description":"Checks in this category that failed.","default":0,"example":0}},"type":"object","required":["score"],"title":"SEOCategoryScore","description":"One category's slice of the overall score."},"SEOCheckResult":{"properties":{"id":{"type":"string","title":"Id","description":"Identifier of the check. Stable across scans, but a check can be missing from a later scan rather than change status, because Base44 collapses cascading failures into their root cause.","example":"llms_txt"},"category":{"type":"string","title":"Category","description":"Which part of the score this check feeds: `meta_tags`, `crawlability`, `structured_data`, `ai_discoverability`, `content_quality`, or `indexing_performance` when Google Search Console integration is enabled for you.","example":"ai_discoverability"},"title":{"type":"string","title":"Title","description":"Short name of the check, phrased for the finding rather than the subject, so it changes with `status`.","example":"AI-readable site guide is off"},"status":{"type":"string","enum":["pass","warn","fail"],"title":"Status","description":"Outcome of the check. A `warn` scores 50 against the category and a `fail` scores 0. Not every check can return all three.","example":"warn"},"description":{"type":"string","title":"Description","description":"What the check found, written for the app's owner.","example":"Turn on the AI site guide so AI engines can understand your app."},"details":{"items":{},"type":"array","title":"Details","description":"Supporting detail for the finding, usually one object per page or entity involved. The keys differ per check, so treat the entries as opaque.","example":[{"issue":"Duplicate title","page":"Pricing"}]},"fix_action":{"anyOf":[{"$ref":"#/components/schemas/SEOFixAction"},{"type":"null"}],"description":"The remedy the Base44 app editor offers, or `null` when the check passed or can't be fixed in-app."},"info_only":{"type":"boolean","title":"Info Only","description":"Environmental findings, such as DNS or hosting issues that can't be resolved from inside the app, set this to `true`. Everything else is `false`.","default":false,"example":false}},"type":"object","required":["id","category","title","status","description"],"title":"SEOCheckResult","description":"One check from the SEO scan."},"SEOFixAction":{"properties":{"type":{"type":"string","title":"Type","description":"Identifier of the action that fixes this check. Apply it with [Apply SEO fix](/api-reference/apply-seo-fix). See [Fix actions](/developers/references/apps-api/sections/seo#fix-actions) for what each one does.","example":"enable_llms_txt"},"label":{"type":"string","title":"Label","description":"Label the app editor shows on the button for this action.","example":"Enable AI site guide"},"params":{"additionalProperties":true,"type":"object","title":"Params","description":"Arguments to send with the action to [Apply SEO fix](/api-reference/apply-seo-fix). Most actions carry none. The keys differ per action, so send them as they are.","example":{}}},"type":"object","required":["type","label"],"title":"SEOFixAction","description":"The remedy Base44 offers for a check."},"SEOFixResponse":{"properties":{"result":{"$ref":"#/components/schemas/SEOFixResult","description":"Outcome of the action."}},"type":"object","required":["result"],"title":"SEOFixResponse"},"SEOFixResult":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the action was applied. It's `false` when the action had nothing to apply, and `message` says why.","example":true},"message":{"type":"string","title":"Message","description":"Summary of the outcome, written for the app's owner.","example":"llms.txt enabled"},"redirect":{"anyOf":[{"type":"string","enum":["overview","app-settings","seo-meta-tags","domains","publish"]},{"type":"null"}],"title":"Redirect","description":"App editor screen where the app's owner makes this fix, either `\"overview\"`, `\"app-settings\"`, `\"seo-meta-tags\"`, `\"domains\"`, or `\"publish\"`. The key is present only for the actions that change nothing.","example":"domains"}},"type":"object","required":["success","message"],"title":"SEOFixResult","description":"What one fix action did."},"SEOOverallScore":{"properties":{"overall":{"type":"integer","title":"Overall","description":"Overall SEO score for the app, 0 to 100.","example":82},"grade":{"type":"string","title":"Grade","description":"Letter grade for `overall`: `A`, `B`, `C`, `D` or `F`.","example":"B"},"categories":{"additionalProperties":{"$ref":"#/components/schemas/SEOCategoryScore"},"type":"object","title":"Categories","description":"Per-category breakdown, keyed by the same category names the checks carry: `meta_tags`, `crawlability`, `structured_data`, `ai_discoverability` and `content_quality`, plus `indexing_performance` when Google Search Console integration is enabled for you. Base44 can add another category, so read the map rather than assuming this list is complete. Every category in the map is scored, including one this scan ran no checks for, which scores 100 with all three counts at zero.","example":{"meta_tags":{"failures":0,"passed":4,"score":80,"warnings":1}}}},"type":"object","required":["overall","grade"],"title":"SEOOverallScore","description":"The scan's score, overall and per category."},"SEOPageSettings":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Title tag for this page. The key is absent while the page overrides no title.","example":"Acme CRM"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Meta description for this page. The key is absent while the page overrides no description.","example":"Track leads and deals in one place."},"og_image":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"title":"Og Image","description":"Image URL for the `og:image` and `twitter:image` tags on this page. It must be an `https://` URL with a host and no backslashes, and it cannot exceed 2048 characters. Send an empty string to stop overriding the image.","example":"https://cdn.example.com/acme-og.png"},"noindex":{"type":"boolean","title":"Noindex","description":"Whether the page is hidden from search engines. When `true`, Base44 serves the page with a `noindex, nofollow` robots directive and leaves it out of the `sitemap.xml` it generates. This applies only while the app is search-eligible and `seo_disabled` is `false`. Defaults to `false` while the key is absent.","default":false,"example":false}},"type":"object","title":"SEOPageSettings","description":"The meta tags one page overrides, and whether it's hidden from search engines."},"SEOPageSettingsResponse":{"properties":{"result":{"$ref":"#/components/schemas/SEOPageSettings","description":"Settings saved for the page, or an empty object if the page has none and the call changes nothing."}},"type":"object","required":["result"],"title":"SEOPageSettingsResponse"},"SEOPageVisibilityResponse":{"properties":{"result":{"additionalProperties":{"$ref":"#/components/schemas/ParentHidden"},"type":"object","title":"Result","description":"Each page a hidden parent page covers, keyed by page name. Pages no parent hides aren't listed, so an app with no hidden parent pages gets an empty object.","example":{"UnitDetail":{"hidden_by":"Units","ignored":false},"units/UnitDetail":{"hidden_by":"Units","ignored":false}}}},"type":"object","required":["result"],"title":"SEOPageVisibilityResponse"},"SEOScanReport":{"properties":{"score":{"$ref":"#/components/schemas/SEOOverallScore","description":"The score this scan produced."},"checks":{"items":{"$ref":"#/components/schemas/SEOCheckResult"},"type":"array","title":"Checks","description":"The checks behind the score, in the order Base44 reports them. Not every check it ran appears here. A failure that only restates an upstream one is dropped in favour of the root cause, so an unpublished or login-gated app returns the one gating failure rather than the dozen findings that follow from it.","example":[{"category":"ai_discoverability","description":"Turn on the AI site guide so AI engines can understand your app.","id":"llms_txt","status":"warn","title":"AI-readable site guide is off"}]},"scanned_at":{"type":"string","title":"Scanned At","description":"When the scan ran, as an ISO 8601 UTC timestamp.","example":"2026-09-03T09:15:00+00:00"}},"type":"object","required":["score","scanned_at"],"title":"SEOScanReport","description":"An SEO scan: its score and every check behind it."},"SEOScanReportResponse":{"properties":{"result":{"$ref":"#/components/schemas/SEOScanReport","description":"The scan Base44 just ran."}},"type":"object","required":["result"],"title":"SEOScanReportResponse","description":"A scan Base44 just ran."},"SEOScoreResponse":{"properties":{"result":{"anyOf":[{"$ref":"#/components/schemas/SEOScoreRollup"},{"type":"null"}],"description":"The last scan's score, or `null` if the app has never been scanned."}},"type":"object","title":"SEOScoreResponse","description":"The score rollup."},"SEOScoreRollup":{"properties":{"overall":{"type":"integer","title":"Overall","description":"Overall SEO score for the app, 0 to 100.","example":82},"grade":{"type":"string","title":"Grade","description":"Letter grade for `overall`: `A`, `B`, `C`, `D` or `F`.","example":"B"},"failures":{"type":"integer","title":"Failures","description":"How many checks failed in that scan.","default":0,"example":2},"warnings":{"type":"integer","title":"Warnings","description":"How many checks returned a warning in that scan.","default":0,"example":3},"scanned_at":{"type":"string","title":"Scanned At","description":"When the scan ran, as an ISO 8601 UTC timestamp.","example":"2026-09-03T09:15:00+00:00"}},"type":"object","required":["overall","grade","scanned_at"],"title":"SEOScoreRollup","description":"The last scan's score, without its checks."},"SEOSettingsResource":{"properties":{"seo_disabled":{"type":"boolean","title":"Seo Disabled","description":"Whether you run your own SEO. When `true`, Base44 stops injecting titles, descriptions, Open Graph and Twitter tags, canonical URLs, JSON-LD and prerender snapshots, and serves your own `public/robots.txt` when you ship one. Defaults to `false` while the key is absent. This does not add a `noindex` tag, and a login-gated or private app stays unindexed whatever you set here.","default":false,"example":false},"platform_meta_injection_enabled":{"type":"boolean","title":"Platform Meta Injection Enabled","description":"Whether Base44 injects the title, description, Open Graph and Twitter tag block. Set it to `false` when your deployed `index.html` already carries a complete set. Narrower than `seo_disabled`, which also turns off canonical URLs, JSON-LD and snapshots. Defaults to `true` while the key is absent.","default":true,"example":true},"platform_jsonld_injection_enabled":{"type":"boolean","title":"Platform Jsonld Injection Enabled","description":"Whether Base44 injects its own WebSite, Organization, BreadcrumbList and FAQPage JSON-LD. Set it to `false` when your app ships its own structured data. Defaults to `true` while the key is absent.","default":true,"example":true},"robots_txt_enabled":{"type":"boolean","title":"Robots Txt Enabled","description":"Whether Base44 generates `/robots.txt`. When `false`, your deployed `public/robots.txt` is served instead, and the path returns 404 when you ship none. Defaults to `true` while the key is absent.","default":true,"example":true},"sitemap_xml_enabled":{"type":"boolean","title":"Sitemap Xml Enabled","description":"Whether Base44 generates `/sitemap.xml`. When `false`, your deployed `public/sitemap.xml` is served instead, and the path returns 404 when you ship none. Defaults to `true` while the key is absent.","default":true,"example":true},"llms_txt_enabled":{"type":"boolean","title":"Llms Txt Enabled","description":"Whether Base44 serves a generated `/llms.txt` describing the app to AI crawlers. Defaults to `false` while the key is absent.","default":false,"example":false},"ai_crawlers_enabled":{"type":"boolean","title":"Ai Crawlers Enabled","description":"Whether known AI crawlers are allowed in the generated `robots.txt`. Defaults to `true` while the key is absent.","default":true,"example":true},"synthesized_breadcrumb_enabled":{"type":"boolean","title":"Synthesized Breadcrumb Enabled","description":"Whether Base44 builds a BreadcrumbList for each request path at render time. Set it to `false` to serve the saved `breadcrumb_schema` instead, which is what keeps a hand-written breadcrumb intact. Defaults to `true` while the key is absent.","default":true,"example":true},"canonical_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Canonical Url","description":"Base URL Base44 builds canonical tags from. The app's own published URL is used while the key is absent.","example":"https://acme.com"},"content_query_params":{"items":{"type":"string"},"type":"array","maxItems":25,"title":"Content Query Params","description":"Query-string parameter names whose values serve distinct content, such as `article` for `/guides?article=slug`. Naming one opts its URL variants into prerender warming, which otherwise only covers paths. Send at most 25 names, counted before the list is cleaned up, and match each name's casing exactly. Names are trimmed, and blank or repeated entries are dropped, so the list you read back can be shorter than the one you sent. Defaults to an empty list, meaning no query variants are warmed.","example":["article"]},"custom_json_ld":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Custom Json Ld","description":"Extra JSON-LD entries Base44 injects alongside its own. Each entry needs a `json_ld_template` key holding the markup object to inject, matching the shape [Preview JSON-LD](/api-reference/preview-json-ld) returns. The key is absent while you have added none.","example":[{"confidence":"0.85","entity_name":"Product","field_mapping":{"name":"title"},"json_ld_template":{"@context":"https://schema.org","@type":"Product","name":"Acme CRM"},"schema_type":"Product"}]},"website_schema":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Website Schema","description":"Saved WebSite JSON-LD object. The key is absent while none is saved.","example":{"@context":"https://schema.org","@type":"WebSite","name":"Acme CRM","url":"https://acme.com"}},"organization_schema":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Organization Schema","description":"Saved Organization JSON-LD object. The key is absent while none is saved.","example":{"@context":"https://schema.org","@type":"Organization","name":"Acme","url":"https://acme.com"}},"breadcrumb_schema":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Breadcrumb Schema","description":"Saved BreadcrumbList JSON-LD object. It is served only while `synthesized_breadcrumb_enabled` is `false`. The key is absent while none is saved.","example":{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[]}},"faq_schema":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Faq Schema","description":"Saved FAQPage JSON-LD object. The key is absent while none is saved.","example":{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[]}},"pages":{"additionalProperties":{"$ref":"#/components/schemas/SEOPageSettings"},"type":"object","title":"Pages","description":"Per-page tag overrides, keyed by the page's component name as it appears in the app's code. The key is absent while no page overrides anything.","example":{"Home":{"description":"Track leads and deals in one place.","og_image":"https://cdn.example.com/acme-og.png","title":"Acme CRM"}}}},"type":"object","title":"SEOSettingsResource","description":"An app's saved SEO settings."},"SEOSettingsResponse":{"properties":{"result":{"$ref":"#/components/schemas/SEOSettingsResource","description":"The saved settings. This is an empty object for an app whose SEO settings have never been changed."}},"type":"object","required":["result"],"title":"SEOSettingsResponse","description":"An app's SEO settings."},"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."},"SandboxStatusResponse":{"properties":{"app_id":{"type":"string","title":"App Id","description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"},"status":{"type":"string","title":"Status","description":"Stored sandbox status. Either `unclaimed`, `claimed`, or `expired`.","example":"unclaimed"},"claimable_sandbox_id":{"type":"string","title":"Claimable Sandbox Id","description":"Stripe claimable sandbox ID.","example":"clmsbx_T7mK2p9Q4r6S8v"},"claim_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Claim Url","description":"Onboarding link, or `null` when none is stored, you are a workspace viewer, or the app's workspace restricts payment management. Treat this link as a credential.","example":"https://dashboard.stripe.com/claim/example"},"stripe_account_id":{"type":"string","title":"Stripe Account Id","description":"Stripe account ID for the sandbox.","example":"acct_T7mK2p9Q4r6S8v"},"is_claimable":{"type":"boolean","title":"Is Claimable","description":"Whether the sandbox is unclaimed, has not expired, has a stored claim link, and the app's workspace allows payment management.","example":true},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At","description":"Sandbox expiration as a UTC timestamp in ISO 8601 format, or `null` when no expiration is stored.","example":"2026-10-25T10:00:00"}},"type":"object","required":["app_id","status","claimable_sandbox_id","claim_url","stripe_account_id","is_claimable","expires_at"],"title":"SandboxStatusResponse","description":"Sandbox status response."},"ScheduledPayout":{"properties":{"date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Date","description":"Date the scheduled funds are paid out, or `null` when they're spread over more than one date.","example":"2026-07-19"},"money":{"$ref":"#/components/schemas/PayoutMoney","description":"Total amount scheduled to be paid out."}},"type":"object","required":["date","money"],"title":"ScheduledPayout"},"ScreenshotLink":{"properties":{"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"Signed link to the thread's screenshot, valid for one year, or `null` when the thread has no screenshot.","example":"https://static.base44.com/images/private/6820f3a4e7b91d003c45a1f2/4b1e9c2a7_shot.png?token=eyJhbGciOi"}},"type":"object","required":["url"],"title":"ScreenshotLink"},"SearchFileResult":{"properties":{"path":{"type":"string","title":"Path","description":"Path of the file, relative to the app root.","example":"src/pages/Home.jsx"},"matches":{"items":{"$ref":"#/components/schemas/SearchMatch"},"type":"array","title":"Matches","description":"The file's matching lines, in order.","example":[{"line_number":12,"preview":"  const [count, setCount] = useState(0);"}]}},"type":"object","required":["path","matches"],"title":"SearchFileResult","description":"The matching lines in one file."},"SearchFiles":{"properties":{"query":{"type":"string","maxLength":200,"minLength":2,"title":"Query","description":"Text to find, 2 to 200 characters on a single line. It's matched literally.","example":"useState("},"case_sensitive":{"type":"boolean","title":"Case Sensitive","description":"Whether the match respects case.","default":false,"example":false}},"type":"object","required":["query"],"title":"SearchFiles","description":"The text to find."},"SearchMatch":{"properties":{"line_number":{"type":"integer","title":"Line Number","description":"Number of the line in the file, starting at 1.","example":12},"preview":{"type":"string","title":"Preview","description":"The line, cut to at most 300 characters around the match.","example":"  const [count, setCount] = useState(0);"}},"type":"object","required":["line_number","preview"],"title":"SearchMatch","description":"One line that contains the text."},"SearchResults":{"properties":{"files":{"items":{"$ref":"#/components/schemas/SearchFileResult"},"type":"array","title":"Files","description":"Each file with at least one match.","example":[{"matches":[{"line_number":12,"preview":"  const [count, setCount] = useState(0);"}],"path":"src/pages/Home.jsx"}]},"truncated":{"type":"boolean","title":"Truncated","description":"`true` when the search stopped at 500 matching lines or 10 seconds, so there can be more matches.","example":false},"skipped_files":{"type":"integer","title":"Skipped Files","description":"Number of files left out of the search because they're over 5 MB or aren't text.","example":0}},"type":"object","required":["files","truncated","skipped_files"],"title":"SearchResults","description":"The files that contain the text."},"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"},"SecretTransferDecisionRequest":{"properties":{"secret_name":{"type":"string","minLength":1,"title":"Secret Name","description":"Name of the secret, from `transfer_package.secrets` in the preflight result.","example":"STRIPE_API_KEY"},"action":{"type":"string","enum":["transfer_value","require_new_value"],"title":"Action","description":"Either `transfer_value`, to copy the secret's value to the new owner, or `require_new_value`, to clear it so the new owner sets their own.","example":"require_new_value"}},"type":"object","required":["secret_name","action"],"title":"SecretTransferDecisionRequest","description":"Sender decision for one app secret. Values are never sent by the client."},"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."},"fingerprint":{"type":"string","title":"Fingerprint","description":"Stable ID for this finding. Send it to [Ignore security finding](/api-reference/ignore-security-finding) to hide the finding across rescans. It changes when the function's file changes, so an ignored finding comes back once the file is edited.","example":"3f9a1c0e8b7d4a6f2e5c9b1d0a8f7e6c5b4a3d2e1f0c9b8a7d6e5f4c3b2a1d0e"}},"type":"object","required":["file_path","description","fingerprint"],"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"},"fingerprint":{"type":"string","title":"Fingerprint","description":"Stable ID for this finding. Send it to [Ignore security finding](/api-reference/ignore-security-finding) to hide the finding across rescans. Every vulnerability in the same version of a package shares one fingerprint, so ignoring one ignores them all. It changes when the installed version changes or the package's advisories, their severities, or their fixed versions change, so an ignored package comes back then.","example":"3f9a1c0e8b7d4a6f2e5c9b1d0a8f7e6c5b4a3d2e1f0c9b8a7d6e5f4c3b2a1d0e"}},"type":"object","required":["package_name","current_version","vulnerable_range","fixed_version","vuln_id","severity","summary","advisory_url","fingerprint"],"title":"SecurityDependencyVulnerability","description":"A known vulnerability in one of the app's npm dependencies."},"SecurityFindingIgnoreStatus":{"properties":{"status":{"type":"string","title":"Status","description":"Always `ok`, including when nothing changed.","example":"ok"}},"type":"object","required":["status"],"title":"SecurityFindingIgnoreStatus","description":"Confirms the call was accepted."},"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."},"fingerprint":{"type":"string","title":"Fingerprint","description":"Stable ID for this finding. Send it to [Ignore security finding](/api-reference/ignore-security-finding) to hide the finding across rescans. It changes when the file changes, so an ignored finding comes back once the file is edited.","example":"3f9a1c0e8b7d4a6f2e5c9b1d0a8f7e6c5b4a3d2e1f0c9b8a7d6e5f4c3b2a1d0e"}},"type":"object","required":["file_path","description","fingerprint"],"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`. Turn it on with [Update security headers](/api-reference/update-security-headers), or hide the recommendation with [Dismiss header recommendation](/api-reference/dismiss-header-recommendation).","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."},"SecurityHeaderRecommendationDismissed":{"properties":{"status":{"type":"string","title":"Status","description":"Always `ok`, including when there was nothing to dismiss.","example":"ok"}},"type":"object","required":["status"],"title":"SecurityHeaderRecommendationDismissed","description":"Confirms the header recommendation is dismissed."},"SecurityHeadersSettingsResponse":{"properties":{"result":{"$ref":"#/components/schemas/SecurityHeadersSettingsResult","description":"The app's security header settings."}},"type":"object","required":["result"],"title":"SecurityHeadersSettingsResponse","description":"The app's security header settings."},"SecurityHeadersSettingsResult":{"properties":{"prevent_iframe_embedding":{"type":"boolean","title":"Prevent Iframe Embedding","description":"Whether the app blocks every site from showing it in a frame (`true`) or not (`false`). When `true`, the published app sends `X-Frame-Options: DENY`.","example":false},"restrict_browser_features":{"type":"boolean","title":"Restrict Browser Features","description":"Whether the published app sends a restrictive `Permissions-Policy` header (`true`) or not (`false`). It lets only the app's own pages use the camera, microphone, location, payment, and USB features, and turns off the magnetometer and gyroscope.","example":false},"embedding_origins":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Embedding Origins","description":"Sites the app itself allows to frame it, or `null` when the app has no list of its own.","example":["https://partners.acme.com"]},"org_embedding_origins":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Org Embedding Origins","description":"Sites the app's workspace allows to frame its apps, or `null` when the workspace sets no list or its plan doesn't include one.","example":["https://partners.acme.com"]},"org_prevent_iframe_embedding":{"type":"boolean","title":"Org Prevent Iframe Embedding","description":"Whether the app's workspace blocks framing for all of its apps (`true`) or not (`false`).","example":false},"app_policy":{"$ref":"#/components/schemas/AppEmbeddingPolicyResult","description":"The framing policy set on the app itself."},"effective_policy":{"$ref":"#/components/schemas/EffectiveEmbeddingPolicyResult","description":"The framing policy the published app actually sends, after the workspace policy is applied."},"app_allowlist_locked_by_workspace":{"type":"boolean","title":"App Allowlist Locked By Workspace","description":"Whether the app's workspace controls which sites can frame its apps (`true`), so the app can't set a list of its own, or not (`false`).","example":false}},"type":"object","required":["prevent_iframe_embedding","restrict_browser_features","embedding_origins","org_embedding_origins","org_prevent_iframe_embedding","app_policy","effective_policy","app_allowlist_locked_by_workspace"],"title":"SecurityHeadersSettingsResult","description":"Security header settings for one app."},"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},"fingerprint":{"type":"string","title":"Fingerprint","description":"Stable ID for this finding. Send it to [Ignore security finding](/api-reference/ignore-security-finding) to hide the finding across rescans. It changes when the entity's fields change, so an ignored recommendation comes back then.","example":"3f9a1c0e8b7d4a6f2e5c9b1d0a8f7e6c5b4a3d2e1f0c9b8a7d6e5f4c3b2a1d0e"}},"type":"object","required":["entity_name","description","create_rule","read_rule","update_rule","delete_rule","fingerprint"],"title":"SecurityRlsRecommendation","description":"A recommended row-level security rule set for one entity."},"SecurityRlsRecommendationDismissed":{"properties":{"status":{"type":"string","title":"Status","description":"Always `ok`. The recommendation is no longer in the scan result.","example":"ok"}},"type":"object","required":["status"],"title":"SecurityRlsRecommendationDismissed","description":"Confirms that the recommendation left the scan result."},"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 with [Enable core integration protection](/api-reference/enable-core-integration-protection) 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"},"ignored_fingerprints":{"items":{"type":"string"},"type":"array","title":"Ignored Fingerprints","description":"Fingerprints of the findings that have been ignored with [Ignore security finding](/api-reference/ignore-security-finding). Those findings are still in their lists, so leave out any whose `fingerprint` appears here to see only the open ones. An entry that matches no finding here stays until the next scan drops it.","example":["3f9a1c0e8b7d4a6f2e5c9b1d0a8f7e6c5b4a3d2e1f0c9b8a7d6e5f4c3b2a1d0e"]},"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","ignored_fingerprints","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},"stream":{"anyOf":[{"$ref":"#/components/schemas/SecurityScanStreamProgress"},{"type":"null"}],"description":"Partial results from a scan that is still running, or `null` when there are none. It appears only while `status` is `scanning`, and only for callers inside the progressive-results rollout. `result` above keeps its own meaning — the previous scan's findings — so ignoring this field preserves the earlier behavior of waiting for the whole scan."}},"type":"object","required":["status","result","static_code_enabled","stream"],"title":"SecurityScanStatus","description":"The state of an app's security scan, and its findings when it has any."},"SecurityScanStreamProgress":{"properties":{"result":{"$ref":"#/components/schemas/SecurityScanFindings","description":"What the areas that have already reported found, in the same shape as `result`. An area that has not reported yet contributes an empty list, which is not the same as a clean result, so read `sections` before concluding there is nothing to find."},"sections":{"additionalProperties":{"type":"string"},"type":"object","title":"Sections","description":"How each area of the scan is doing, keyed by area: `rls`, `hardcoded_secrets`, `backend_functions`, `dependency_vulnerabilities` and `static_code`. `checking` means the area has not reported yet, `completed` means its findings are in this block's `result`, `failed` means it could not be checked, and `not_run` means it does not apply to this app. Only `completed` means the area's findings are settled.","example":{"backend_functions":"completed","dependency_vulnerabilities":"checking","hardcoded_secrets":"completed","rls":"completed","static_code":"not_run"}}},"type":"object","required":["result","sections"],"title":"SecurityScanStreamProgress","description":"How far a still-running security scan has got, area by area."},"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`, `ai_vulnerability` 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"}]},"fingerprint":{"type":"string","title":"Fingerprint","description":"Stable ID for this finding. Send it to [Ignore security finding](/api-reference/ignore-security-finding) to hide the finding across rescans. It stays the same when only the wording or line number changes, and changes when the file, `category`, or `evidence` does, so an ignored finding comes back then.","example":"3f9a1c0e8b7d4a6f2e5c9b1d0a8f7e6c5b4a3d2e1f0c9b8a7d6e5f4c3b2a1d0e"}},"type":"object","required":["title","severity","confidence","category","file_path","line_number","evidence","attack_scenario","impact","recommendation","fingerprint"],"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."},"SeededTestData":{"properties":{"seeded":{"additionalProperties":{"type":"integer"},"type":"object","title":"Seeded","description":"Records written for each entity, keyed by entity name. `0` when nothing was written.","example":{"Project":5,"Task":3}},"total":{"type":"integer","title":"Total","description":"Records written across all entities.","example":8},"generated_total":{"type":"integer","title":"Generated Total","description":"How many of the records were made up by AI rather than copied from live data.","example":3},"entities_with_data":{"type":"integer","title":"Entities With Data","description":"Entities that got records, or whose own test records were kept.","example":2},"entities_without_prod_data":{"items":{"type":"string"},"type":"array","title":"Entities Without Prod Data","description":"Entities that got nothing copied from live data and didn't report an error. When this call gave up waiting for another seed of the app, every entity is listed.","example":["Task"]},"entities_with_errors":{"items":{"type":"string"},"type":"array","title":"Entities With Errors","description":"Entities whose seeding reported an error, for example because generating records timed out. An entity whose record writes all failed isn't listed. Its `seeded` count is `0`.","example":[]}},"type":"object","required":["seeded","total","generated_total","entities_with_data","entities_without_prod_data","entities_with_errors"],"title":"SeededTestData","description":"What a seed wrote into the app's test data."},"SendMessagePayload":{"properties":{"message_id":{"type":"string","format":"uuid","title":"Message Id","description":"Your ID for the message. A retry with the same ID doesn't start a second turn. Defaults to a new UUID.","example":"3f2504e0-4f89-11d3-9a0c-0305e82c3301"},"content":{"type":"string","maxLength":20000,"title":"Content","description":"The message to the Marketing Agent. Up to 20,000 characters.","example":"Focus on LinkedIn instead of Reddit."}},"type":"object","required":["content"],"title":"SendMessagePayload"},"SetDomainRedirectBody":{"properties":{"target_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Domain","description":"Domain to send this domain's visitors to, or `null` to stop redirecting. It has to be another verified custom domain on the same app.","example":"www.example.com"}},"type":"object","title":"SetDomainRedirectBody"},"SetIntegrationRequest":{"properties":{"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"Every OAuth scope the connection should have. Scopes the app's current connection has and this list leaves out are dropped. Send an empty list for the connector's default scopes.","example":["https://www.googleapis.com/auth/calendar.readonly"]},"connection_config":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Connection Config","description":"Values the connector needs before authorization, keyed by the `name` of each field from [Get connector connection fields](/api-reference/get-connector-connection-fields). Leave it out for connectors that have no fields. Values that differ from the saved ones start a new authorization.","example":{"subdomain":"acme-prod"}}},"type":"object","required":["scopes"],"title":"SetIntegrationRequest","description":"The scopes, and any connection values, the app's connection should have."},"SetIntegrationResponse":{"properties":{"redirect_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Redirect Url","description":"Link to open in a browser so the user can sign in to the provider and approve the scopes. It expires after 10 minutes. Treat it as a credential. The value is `null` when no authorization was started.","example":"https://app.base44.com/api/external-auth/connect/3f9c2a7e5b8d4c1fa6e0d2b7c9a1e4f3"},"connection_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Connection Id","description":"ID of the connection being authorized. Pass it to [Get connector connection status](/api-reference/get-connector-connection-status). When `already_authorized` is `true` it's the existing connection. The value is `null` when `error` is set.","example":"base44_6820f3a4e7b91d003c45a1f7"},"already_authorized":{"type":"boolean","title":"Already Authorized","description":"Whether you already have an active connection with exactly these scopes and connection values (`true`), so there's nothing to open, or not (`false`).","default":false,"example":false},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why no authorization was started, or `null` if one was. Either `different_user`, when another collaborator's active connection already has exactly these scopes, or `service_unavailable`, when the provider can't be reached. Retry later.","example":"different_user"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message","description":"Readable explanation of `error`. It can be `null` even when `error` is set, so branch on `error`.","example":"Integration googlecalendar is already authorized by a different user for this app."}},"type":"object","required":["redirect_url","connection_id"],"title":"SetIntegrationResponse","description":"The authorization that sets the app's connection to the requested scopes, or why none was needed."},"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."},"SetupAnswerItem":{"properties":{"key":{"type":"string","maxLength":40,"title":"Key","description":"Question being answered. Either `\"channels_ok\"`, `\"kpis_ok\"`, `\"budget\"`, `\"competitor_research\"`, or `\"budget_go\"`.","example":"budget"},"selected_label":{"type":"string","maxLength":200,"minLength":1,"title":"Selected Label","description":"The answer as the user would say it. It's recorded as their message in the app's chat, and for `budget` it's the stored budget.","example":"$500 a month"},"value":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Value","description":"For `competitor_research`, whether to research competitors (`true`) or not (`false`). Ignored for other questions.","example":true},"band":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Band","description":"For `budget`, the budget band that selects each KPI's target. Either `\"none\"`, `\"low\"`, `\"mid\"`, or `\"high\"`. Any other value is ignored.","example":"mid"}},"type":"object","required":["key","selected_label"],"title":"SetupAnswerItem"},"SetupAnswerPayload":{"properties":{"stage":{"type":"string","maxLength":40,"title":"Stage","description":"The step you're answering, from the state's `setup_stage`. Either `\"channels\"`, `\"kpis\"`, or `\"budget\"`.","example":"budget"},"reset_epoch":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Reset Epoch","description":"The state's `reset_epoch`, so the answer is rejected if the plan was reset since you read it. Omit it to skip that check.","example":0},"answers":{"items":{"$ref":"#/components/schemas/SetupAnswerItem"},"type":"array","maxItems":4,"minItems":1,"title":"Answers","description":"Answers to the step's questions. Between 1 and 4."},"message_id":{"type":"string","format":"uuid","title":"Message Id","description":"ID for the chat message that records the answer. Use a new UUID for every answer, and the same one when you retry it. A retry with the same ID returns the current state and applies nothing. Defaults to a new UUID.","example":"3f2504e0-4f89-11d3-9a0c-0305e82c3301"}},"type":"object","required":["stage","answers"],"title":"SetupAnswerPayload"},"ShareListResponse":{"properties":{"shares":{"items":{"$ref":"#/components/schemas/ShareSummary"},"type":"array","title":"Shares","description":"The app's group shares.","example":[{"created_by":"jane@acme.com","created_date":"2026-09-14T09:21:37.512000","group_id":"68c9a0f1d2e4b5001f3a7c90","group_name":"Sales team","group_source":"custom","id":"68d1c2e4a9b7f3001e5c8a41","member_count":12,"role":"user","unresolved_count":0}]}},"type":"object","required":["shares"],"title":"ShareListResponse"},"ShareSummary":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the share. Pass it as `share_id` to update or remove the share.","example":"68d1c2e4a9b7f3001e5c8a41"},"group_id":{"type":"string","title":"Group Id","description":"ID of the workspace group the app is shared with.","example":"68c9a0f1d2e4b5001f3a7c90"},"group_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Group Name","description":"Name of the group, or `null` when the group was deleted. A share whose group was deleted grants no access, and you can still remove it.","example":"Sales team"},"group_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Group Source","description":"Where the group comes from: `custom` for a group created in Base44, or `idp` for a group your identity provider syncs over SCIM. `null` when the group was deleted.","example":"custom"},"member_count":{"type":"integer","title":"Member Count","description":"Number of group members with a Base44 account.","default":0,"example":12},"unresolved_count":{"type":"integer","title":"Unresolved Count","description":"Number of group members your identity provider synced who don't have a Base44 account yet. They get access once they have one.","default":0,"example":0},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"App role the share gives the group's members, or `null` when it assigns none and members get the default `user` role.","example":"user"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By","description":"Email of the person who shared the app with the group.","example":"jane@acme.com"},"created_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created Date","description":"When the app was shared with the group, in UTC, as ISO 8601 without a timezone suffix.","example":"2026-09-14T09:21:37.512000"}},"type":"object","required":["id","group_id","group_name","group_source","member_count","unresolved_count","role","created_by","created_date"],"title":"ShareSummary"},"SlugRejected":{"properties":{"detail":{"type":"string","title":"Detail","description":"What is wrong with the slug.","example":"URL slug 'nordwind' is already in use"},"suggestions":{"items":{"type":"string"},"type":"array","title":"Suggestions","description":"Up to five unused slugs close to the one you sent, present only when that slug is taken. Empty when Base44 could not come up with any.","example":["nordwind-furniture","nordwind-shop"]}},"type":"object","required":["detail"],"title":"SlugRejected","description":"Why the slug was not accepted."},"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 from [Generate teaser posts](/api-reference/generate-teaser-posts). They're 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."},"SocialImagePayload":{"properties":{"social_image_url":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"title":"Social Image Url","description":"`https` URL of the image to show in link previews, up to 2048 characters, or `null` to remove the current one. Leaving it out also removes it.","example":"https://storage.base44.com/6820f3a4e7b91d003c45a1f2/7c1e9f2a_share.png"}},"type":"object","title":"SocialImagePayload"},"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"},"StandardError":{"properties":{"code":{"type":"string","title":"Code","description":"Stable machine-readable cause of the failure. Match on this, not on `message`.","example":"not_found"},"message":{"type":"string","title":"Message","description":"Human-readable explanation. It isn't a stable contract.","example":"App not found"},"details":{"additionalProperties":true,"type":"object","title":"Details","description":"Structured context for the failure, or an empty object when there's none.","example":{}}},"type":"object","required":["code","message"],"title":"StandardError"},"StandardErrorEnvelope":{"properties":{"error":{"$ref":"#/components/schemas/StandardError","description":"What went wrong."}},"type":"object","required":["error"],"title":"StandardErrorEnvelope","description":"The error body returned in the standard envelope."},"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."},"StatusObject":{"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/PaywallStatusContext"},{"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":"StatusObject","description":"The app's current build status."},"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."},"StripeCatalogPrice":{"properties":{"id":{"type":"string","title":"Id","description":"Stripe price ID.","example":"price_1T7mK2Q4r6S8v9Ab"},"product":{"type":"string","title":"Product","description":"Stripe product ID this price belongs to.","example":"prod_T7mK2p9Q4r6S8v"},"unit_amount":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Unit Amount","description":"Amount in the currency's smallest unit, or `null` for pricing without a fixed integer unit amount.","example":2500},"currency":{"type":"string","title":"Currency","description":"Currency as a lowercase three-letter ISO 4217 code.","example":"usd"},"active":{"type":"boolean","title":"Active","description":"Whether the price is active.","example":true},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Metadata stored on the price.","example":{"tier":"standard"}},"recurring":{"anyOf":[{"$ref":"#/components/schemas/StripeCatalogRecurring"},{"type":"null"}],"description":"Billing schedule, or `null` for a one-time price.","example":{"interval":"month","interval_count":1,"usage_type":"licensed"}},"livemode":{"type":"boolean","title":"Livemode","description":"`true` when the price is in the live catalog, `false` when it's in the test catalog.","example":false}},"type":"object","required":["id","product","unit_amount","currency","active","metadata","recurring","livemode"],"title":"StripeCatalogPrice","description":"A price in the Stripe account configured for the app."},"StripeCatalogProduct":{"properties":{"id":{"type":"string","title":"Id","description":"Stripe product ID.","example":"prod_T7mK2p9Q4r6S8v"},"name":{"type":"string","title":"Name","description":"Product name.","example":"Studio membership"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Product description, or `null` when none is set.","example":"Access to weekly classes."},"active":{"type":"boolean","title":"Active","description":"Whether the product is active.","example":true},"images":{"items":{"type":"string"},"type":"array","title":"Images","description":"Product image URLs. Neither Create nor Update Stripe product takes an `images` field, so this stays empty unless you add images directly in the Stripe dashboard for the account these credentials point to.","example":["https://example.com/membership.jpg"]},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Metadata stored on the product.","example":{"category":"membership"}},"livemode":{"type":"boolean","title":"Livemode","description":"`true` when the product is in the live catalog, `false` when it's in the test catalog.","example":false}},"type":"object","required":["id","name","description","active","images","metadata","livemode"],"title":"StripeCatalogProduct","description":"A product in the Stripe account configured for the app."},"StripeCatalogRecurring":{"properties":{"interval":{"type":"string","title":"Interval","description":"Billing interval. Either `day`, `week`, `month`, or `year`.","example":"month"},"interval_count":{"type":"integer","title":"Interval Count","description":"Number of intervals between bills.","example":1},"usage_type":{"type":"string","title":"Usage Type","description":"Usage model. Either `licensed` or `metered`.","example":"licensed"}},"type":"object","required":["interval","interval_count","usage_type"],"title":"StripeCatalogRecurring","description":"The recurring billing schedule of a Stripe price."},"StripeIntegrationStatusResponse":{"properties":{"app_id":{"type":"string","title":"App Id","description":"ID of the Base44 app.","example":"6820f3a4e7b91d003c45a1f2"},"stripe_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stripe Mode","description":"Mode inferred from available credentials and sandbox state. Either `sandbox` or `live`, or `null` when neither is available. Defaults to `null`.","example":"sandbox"},"sandbox_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sandbox Status","description":"Stored sandbox status. Either `unclaimed`, `claimed`, or `expired`, or `null` without a sandbox. Defaults to `null`.","example":"unclaimed"},"sandbox_is_claimable":{"type":"boolean","title":"Sandbox Is Claimable","description":"Whether the sandbox is unclaimed, has not expired, has a stored claim link, and the app's workspace allows payment management. Defaults to `false`.","default":false,"example":true},"sandbox_claim_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sandbox Claim Url","description":"Stored claim link, or `null` when the sandbox is not claimable, you are a workspace viewer, or the app's workspace restricts payment management. Treat this link as a credential. Defaults to `null`.","example":"https://dashboard.stripe.com/claim/example"},"sandbox_days_until_expiry":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sandbox Days Until Expiry","description":"Whole days until sandbox expiration, or `null` for a claimed sandbox or when no expiration is stored. Defaults to `null`.","example":30},"sandbox_account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sandbox Account Id","description":"Stripe sandbox account ID, or `null` without a sandbox. Defaults to `null`.","example":"acct_T7mK2p9Q4r6S8v"},"test_mode_configured":{"type":"boolean","title":"Test Mode Configured","description":"Whether test credentials are available. Defaults to `false`.","default":false,"example":true},"live_mode_configured":{"type":"boolean","title":"Live Mode Configured","description":"Whether live credentials or a connected live account are available. This alone does not mean the app has switched to live keys. Defaults to `false`.","default":false,"example":false},"live_mode_method":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Live Mode Method","description":"Available live authentication method. Either `direct_keys` or `platform_auth`, or `null` when neither is available. Direct keys take precedence. Defaults to `null`.","example":"direct_keys"},"live_keys_configured":{"type":"boolean","title":"Live Keys Configured","description":"Whether live key setup finished completely, including the redeploy that picks up the new keys. A partial failure leaves this `false` even after keys are stored. Defaults to `false`.","default":false,"example":false},"live_account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Live Account Id","description":"Connected live Stripe account ID, or `null` before activation. Defaults to `null`.","example":"acct_L8nP3q5R7s9T2v"},"has_tested_payment":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Tested Payment","description":"Whether Stripe reports any checkout session when requested in sandbox mode. A failed lookup also reports `false`, and a session does not prove payment. Otherwise `null`. Defaults to `null`.","example":false}},"type":"object","required":["app_id"],"title":"StripeIntegrationStatusResponse","description":"Comprehensive Stripe integration status response."},"StripeLiveKeysResult":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether live keys were stored and the app was switched to live mode.","example":true},"message":{"type":"string","title":"Message","description":"Human-readable configuration result.","example":"Live mode API keys configured successfully. Test keys have been backed up."},"live_keys_configured":{"type":"boolean","title":"Live Keys Configured","description":"Whether the stored completion flag was confirmed. It can be `false` even after keys were stored if finalization failed.","example":true},"webhook_migrated":{"type":"boolean","title":"Webhook Migrated","description":"Whether this call automatically migrated the webhook. It is `false` when a webhook secret was supplied directly.","example":false}},"type":"object","required":["success","message","live_keys_configured","webhook_migrated"],"title":"StripeLiveKeysResult","description":"The result of storing live Stripe credentials."},"StripeRemovalResult":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the removal completed.","example":true}},"type":"object","required":["success"],"title":"StripeRemovalResult","description":"The result of removing the app's Stripe configuration."},"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"},"SubmittedTemplateListingUpdate":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the template listing.","example":"68b2f0c1a4d9e7001c3b5a22"},"app_id":{"type":"string","title":"App Id","description":"ID of the app the listing was made from.","example":"6820f3a4e7b91d003c45a1f2"},"name":{"type":"string","title":"Name","description":"Name of the listing. For a public template, a new name waits in `pending_metadata` until the update is approved.","example":"CRM starter"},"status":{"type":"string","title":"Status","description":"Review status of the listing. Stays `approved`, because the current version stays live.","example":"approved"},"visibility_scope":{"type":"string","title":"Visibility Scope","description":"`public` for a listing in the Base44 template gallery, or `workspace` for one shared only within the app's workspace.","example":"workspace"},"pending_metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Pending Metadata","description":"Listing details waiting for review, or `null` when none are. Always `null` for a workspace template, whose changes apply right away.","example":{"version":"1.1.0"}},"_update_info":{"$ref":"#/components/schemas/TemplateListingUpdateInfo","description":"Whether the submission copied the app, changed listing details, and applied right away."}},"type":"object","required":["id","app_id","name","status","visibility_scope","pending_metadata","_update_info"],"title":"SubmittedTemplateListingUpdate","description":"The template listing after the update was submitted."},"SuggestionsRequest":{"properties":{"keyword_themes":{"items":{"type":"string"},"type":"array","maxItems":25,"title":"Keyword Themes","description":"Themes the campaign would match on, up to 25.","example":["handmade oak furniture","custom dining tables"]},"landing_page":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Landing Page","description":"Page the campaign would send clicks to, as an `http` or `https` URL. Required: a request without one is rejected with a 400.","example":"https://example.com/furniture"},"geo_targets":{"items":{"type":"string"},"type":"array","maxItems":50,"title":"Geo Targets","description":"Locations to plan for, up to 50, as Google geo target constant IDs (`geoTargetConstants/1023191`, or just `1023191`). Entries in any other form are dropped, and at least one usable entry is required.","example":["1023191"]},"language_code":{"type":"string","title":"Language Code","description":"Two-letter code of the language the ads would run in. A missing or unsupported code plans in English.","default":"","example":"en"},"business_name":{"type":"string","title":"Business Name","description":"Business name, which sharpens Google's forecast. Only the first 120 characters are used.","default":"","example":"Oakline Furniture"}},"type":"object","title":"SuggestionsRequest"},"SuggestionsResponse":{"properties":{"suggestions":{"items":{"type":"string"},"type":"array","title":"Suggestions","description":"Short prompts, each describing events worth tracking in this app. They don't include `prefix`.","example":["Track recipe saves and shares","Log meal plan creation","Record grocery list exports","Monitor search queries and result clicks"]},"prefix":{"type":"string","title":"Prefix","description":"Text to put in front of each prompt before you send it to the app's AI chat.","example":"Using Event Tracking, "}},"type":"object","required":["suggestions","prefix"],"title":"SuggestionsResponse"},"Superagent":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the agent. Use it as `agent_id` in the other Superagent endpoints, such as [Create Superagent conversation](/api-reference/create-superagent-conversation).","example":"6820f3a4e7b91d003c45a1f2"},"name":{"type":"string","title":"Name","description":"Name of the agent.","example":"Sales assistant"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Description of the agent, or `null` if none was set.","example":"Answers questions about our weekly sales."},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url","description":"URL of the agent's logo, or `null` if none was set.","example":"https://cdn.acme.com/sales-assistant.png"}},"type":"object","required":["id","name"],"title":"Superagent"},"SuperagentConversation":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the conversation.","example":"68a1c2e4f0b9d3002e7a5c11"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Title Base44 generates from the conversation, or `null` until one is generated.","example":"Weekly sales summary"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Metadata stored on the conversation, including what you sent to [Create Superagent conversation](/api-reference/create-superagent-conversation) when it created it.","example":{"source":"crm-sync"}},"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"Time the conversation was created, as a UTC timestamp in ISO 8601 format.","example":"2026-08-01T09:15:00Z"},"updated_date":{"type":"string","format":"date-time","title":"Updated Date","description":"Time the conversation last changed, as a UTC timestamp in ISO 8601 format.","example":"2026-08-02T14:30:00Z"},"messages":{"items":{"$ref":"#/components/schemas/SuperagentMessage"},"type":"array","title":"Messages","description":"The conversation's latest messages, oldest first, up to 200.","example":[{"content":"Summarize today's sales.","id":"3f2504e0-4f89-41d3-9a0c-0305e82c3301","metadata":{"created_date":"2026-08-02T14:29:51Z"},"role":"user"},{"content":"Here's a summary of today's sales.","id":"6b8f1c2e-9a4d-4e7b-8c3f-1d2e3f4a5b6c","metadata":{"created_date":"2026-08-02T14:30:00Z"},"role":"assistant"}]}},"type":"object","required":["id","title","metadata","created_date","updated_date","messages"],"title":"SuperagentConversation","description":"A Superagent conversation with its latest messages."},"SuperagentConversationSummary":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the conversation.","example":"68a1c2e4f0b9d3002e7a5c11"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Title Base44 generates from the conversation, or `null` until one is generated.","example":"Weekly sales summary"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Metadata stored on the conversation, including what you sent to [Create Superagent conversation](/api-reference/create-superagent-conversation) when it created it.","example":{"source":"crm-sync"}},"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"Time the conversation was created, as a UTC timestamp in ISO 8601 format.","example":"2026-08-01T09:15:00Z"},"updated_date":{"type":"string","format":"date-time","title":"Updated Date","description":"Time the conversation last changed, as a UTC timestamp in ISO 8601 format.","example":"2026-08-02T14:30:00Z"}},"type":"object","required":["id","title","metadata","created_date","updated_date"],"title":"SuperagentConversationSummary","description":"A Superagent conversation, without its messages."},"SuperagentMemory":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the memory.","example":"68a1c9b2f0b9d3002e7a5c3d"},"content":{"type":"string","title":"Content","description":"What the agent remembers.","example":"Prefers weekly reports on Monday mornings."},"scope":{"type":"string","enum":["global","user"],"title":"Scope","description":"Who the memory applies to. `global` memories apply to every user of the agent, and `user` memories only to you.","example":"user"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id","description":"ID of the user the memory belongs to when `scope` is `user`, or `null` for a `global` memory.","example":"6820f41be7b91d003c45a20a"},"conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversation Id","description":"ID of the conversation the agent created the memory in, or `null` when it wasn't created in one.","example":"68a1c2e4f0b9d3002e7a5c11"},"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"Time the memory was created, as a UTC timestamp in ISO 8601 format.","example":"2026-08-02T14:30:00Z"},"updated_date":{"type":"string","format":"date-time","title":"Updated Date","description":"Time the memory last changed, as a UTC timestamp in ISO 8601 format.","example":"2026-08-02T14:30:00Z"}},"type":"object","required":["id","content","scope","user_id","conversation_id","created_date","updated_date"],"title":"SuperagentMemory","description":"Something the Superagent remembers."},"SuperagentMessage":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the message.","example":"3f2504e0-4f89-41d3-9a0c-0305e82c3301"},"role":{"type":"string","enum":["user","assistant"],"title":"Role","description":"Who wrote the message. Either `user` or `assistant`.","example":"assistant"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content","description":"Text of the message, or `null` when the message only carries tool calls.","example":"Here's a summary of today's sales."},"file_urls":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"File Urls","description":"URLs of files attached to the message, or `null` when it has none.","example":["https://storage.base44.com/6820f3a4e7b91d003c45a1f2/report.pdf"]},"tool_calls":{"anyOf":[{"items":{"$ref":"#/components/schemas/SuperagentToolCall"},"type":"array"},{"type":"null"}],"title":"Tool Calls","description":"Tools the agent called while writing the message, or `null` when it called none.","example":[{"id":"toolu_01A09q90qw90lq917835lq9","name":"web_search","requires_user_input":false,"status":"success"}]},"metadata":{"$ref":"#/components/schemas/SuperagentMessageMetadata","description":"When the message was created."}},"type":"object","required":["id","role","content","metadata"],"title":"SuperagentMessage","description":"A message in a Superagent conversation."},"SuperagentMessageMetadata":{"properties":{"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"Time the message was created, as a UTC timestamp in ISO 8601 format.","example":"2026-08-02T14:30:00Z"}},"type":"object","required":["created_date"],"title":"SuperagentMessageMetadata"},"SuperagentToolCall":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the tool call.","example":"toolu_01A09q90qw90lq917835lq9"},"name":{"type":"string","title":"Name","description":"Name of the tool the agent called.","example":"web_search"},"status":{"type":"string","enum":["running","success","error","stopped","waiting_for_user_input"],"title":"Status","description":"Where the tool call is. One of `running`, `success`, `error`, `stopped`, or `waiting_for_user_input`.","example":"success"},"requires_user_input":{"type":"boolean","title":"Requires User Input","description":"Whether the tool call is waiting for the user to answer or approve something before the agent continues.","example":false}},"type":"object","required":["id","name","status","requires_user_input"],"title":"SuperagentToolCall"},"SuperagentWebhook":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the webhook.","example":"68a1d0f4f0b9d3002e7a5c52"},"target_url":{"type":"string","title":"Target Url","description":"HTTPS URL Base44 sends the events to.","example":"https://example.com/hooks/agent"},"events":{"items":{"type":"string","enum":["message.created","message.completed"]},"type":"array","title":"Events","description":"Events the webhook receives. `message.created` fires when a message is added to a conversation, and `message.completed` when the agent finishes a reply.","example":["message.completed"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Your label for the webhook, or `null` when it has none.","example":"CRM sync"},"has_secret":{"type":"boolean","title":"Has Secret","description":"Whether deliveries are signed. When `true`, each delivery carries an `X-Base44-Signature` header, an HMAC-SHA256 of the body.","example":true},"last_trigger_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Trigger Time","description":"Time of the last delivery attempt, successful or not, as a UTC timestamp in ISO 8601 format, or `null` before the first one.","example":"2026-08-02T14:30:00Z"},"last_error":{"anyOf":[{"$ref":"#/components/schemas/SuperagentWebhookError"},{"type":"null"}],"description":"What went wrong on the last failed delivery, or `null` when the last delivery succeeded or none has failed."},"consecutive_failures":{"type":"integer","title":"Consecutive Failures","description":"Deliveries that failed in a row. After 20, Base44 turns the webhook off and sets `disabled_at`.","example":0},"disabled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Disabled At","description":"Time the webhook was turned off, as a UTC timestamp in ISO 8601 format, or `null` while it's on. Turn it back on with `enabled: true` in [Update Superagent webhook](/api-reference/update-superagent-webhook).","example":"2026-08-03T08:00:00Z"},"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"Time the webhook was created, as a UTC timestamp in ISO 8601 format.","example":"2026-08-01T09:15:00Z"},"updated_date":{"type":"string","format":"date-time","title":"Updated Date","description":"Time the webhook last changed, as a UTC timestamp in ISO 8601 format.","example":"2026-08-02T14:30:00Z"}},"type":"object","required":["id","target_url","events","description","has_secret","last_trigger_time","last_error","consecutive_failures","disabled_at","created_date","updated_date"],"title":"SuperagentWebhook","description":"A webhook subscription on a Superagent."},"SuperagentWebhookError":{"properties":{"message":{"type":"string","title":"Message","description":"What went wrong on the last delivery.","example":"Non-2xx response: 500"},"attempted_at":{"type":"string","format":"date-time","title":"Attempted At","description":"Time of the failed delivery, as a UTC timestamp in ISO 8601 format.","example":"2026-08-02T14:30:00+00:00"},"status_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Status Code","description":"HTTP status your endpoint answered with, when it answered with a non-2xx status.","example":500},"response_body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Response Body","description":"Start of your endpoint's response body, up to 512 bytes, when it answered with a non-2xx status.","example":"Internal Server Error"},"error_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Type","description":"Kind of failure when the request didn't get an answer, such as a timeout or a refused connection.","example":"ConnectTimeout"}},"type":"object","required":["message","attempted_at"],"title":"SuperagentWebhookError"},"SuperagentWebhookTestResult":{"properties":{"ok":{"type":"boolean","title":"Ok","description":"Whether your endpoint answered with a 2xx status.","example":true},"status_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Status Code","description":"HTTP status your endpoint answered with. Present only when it answered.","example":200},"response_body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Response Body","description":"Start of your endpoint's response body, up to 1,024 bytes. Present only when it answered.","example":"ok"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why the delivery didn't get an answer, such as a timeout or a URL Base44 refuses to call. Present only when it didn't.","example":"ConnectTimeout: timed out"}},"type":"object","required":["ok"],"title":"SuperagentWebhookTestResult","description":"The outcome of a test delivery."},"SuperagentWebhookWithSecret":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the webhook.","example":"68a1d0f4f0b9d3002e7a5c52"},"target_url":{"type":"string","title":"Target Url","description":"HTTPS URL Base44 sends the events to.","example":"https://example.com/hooks/agent"},"events":{"items":{"type":"string","enum":["message.created","message.completed"]},"type":"array","title":"Events","description":"Events the webhook receives. `message.created` fires when a message is added to a conversation, and `message.completed` when the agent finishes a reply.","example":["message.completed"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Your label for the webhook, or `null` when it has none.","example":"CRM sync"},"has_secret":{"type":"boolean","title":"Has Secret","description":"Whether deliveries are signed. When `true`, each delivery carries an `X-Base44-Signature` header, an HMAC-SHA256 of the body.","example":true},"last_trigger_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Trigger Time","description":"Time of the last delivery attempt, successful or not, as a UTC timestamp in ISO 8601 format, or `null` before the first one.","example":"2026-08-02T14:30:00Z"},"last_error":{"anyOf":[{"$ref":"#/components/schemas/SuperagentWebhookError"},{"type":"null"}],"description":"What went wrong on the last failed delivery, or `null` when the last delivery succeeded or none has failed."},"consecutive_failures":{"type":"integer","title":"Consecutive Failures","description":"Deliveries that failed in a row. After 20, Base44 turns the webhook off and sets `disabled_at`.","example":0},"disabled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Disabled At","description":"Time the webhook was turned off, as a UTC timestamp in ISO 8601 format, or `null` while it's on. Turn it back on with `enabled: true` in [Update Superagent webhook](/api-reference/update-superagent-webhook).","example":"2026-08-03T08:00:00Z"},"created_date":{"type":"string","format":"date-time","title":"Created Date","description":"Time the webhook was created, as a UTC timestamp in ISO 8601 format.","example":"2026-08-01T09:15:00Z"},"updated_date":{"type":"string","format":"date-time","title":"Updated Date","description":"Time the webhook last changed, as a UTC timestamp in ISO 8601 format.","example":"2026-08-02T14:30:00Z"},"secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Secret","description":"The signing secret, returned only in the response that generates it. Store it, because no other response shows it again.","example":"Rk3x9QeN0vZ8bL2mT6yH4cJ1wP7sD5fG0aX9kV3uE8o"}},"type":"object","required":["id","target_url","events","description","has_secret","last_trigger_time","last_error","consecutive_failures","disabled_at","created_date","updated_date"],"title":"SuperagentWebhookWithSecret","description":"A webhook subscription, with its signing secret when one was just generated."},"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."},"SyncedCampaign":{"properties":{"google_campaign_id":{"type":"string","title":"Google Campaign Id","description":"The campaign's ID in Google Ads. Match it against `google_campaign_id` from [List campaigns](/api-reference/list-google-ads-campaigns) to find Base44's `id` for the campaign.","example":"21098765432"},"name":{"type":"string","title":"Name","description":"Name shown for the campaign.","example":"Spring sale in Berlin"},"status":{"type":"string","title":"Status","description":"Serving state Google reports, `ENABLED` or `PAUSED`.","example":"ENABLED"},"campaign_type":{"type":"string","title":"Campaign Type","description":"Campaign type, passed through as Google reports it. Base44 creates `SMART` and `PERFORMANCE_MAX`. The other values appear only on campaigns created outside Base44.","example":"SMART"},"impressions":{"type":"integer","title":"Impressions","description":"Times the campaign's ads were shown.","example":15420},"clicks":{"type":"integer","title":"Clicks","description":"Clicks the campaign received.","example":612},"cost_micros":{"type":"integer","title":"Cost Micros","description":"Spend in micros of the account currency, where 1,000,000 micros is one unit, so `248000000` is 248.00.","example":248000000},"conversions":{"type":"number","title":"Conversions","description":"Conversions Google attributed to the campaign.","example":31.0},"review_status":{"type":"string","title":"Review Status","description":"Google's policy review status as Base44 last recorded it (`REVIEWED`, `UNDER_REVIEW`, …). A sync doesn't refresh it, and it's empty on a campaign the sync just added.","example":"REVIEWED"},"serving_status":{"type":"string","title":"Serving Status","description":"Google's serving status, passed through as Google reports it.","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"]}},"type":"object","required":["google_campaign_id","name","status","campaign_type","impressions","clicks","cost_micros","conversions","review_status","serving_status","primary_status","primary_status_reasons"],"title":"SyncedCampaign","description":"One campaign as Google reported it during a sync."},"TeaserStatus":{"type":"string","enum":["generating","ready","failed"],"title":"TeaserStatus"},"TeaserSummary":{"properties":{"status":{"anyOf":[{"$ref":"#/components/schemas/TeaserStatus"},{"type":"null"}],"description":"State of the teaser generation. Either `\"generating\"`, `\"ready\"`, or `\"failed\"`, or `null` if it was never started 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, so 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 standalone teaser posts for an app and the state of their generation."},"TemplateEmailStatsDoc":{"properties":{"template_key":{"type":"string","title":"Template Key","description":"Which template the counts are for: its `name`, or its `id` for emails sent by template ID. `_none` groups emails sent without a template. A deleted template keeps its entry for the emails it sent.","example":"Welcome"},"totals":{"$ref":"#/components/schemas/EmailStatTotalsDoc","description":"Totals for the period."},"previous_totals":{"anyOf":[{"$ref":"#/components/schemas/EmailStatTotalsDoc"},{"type":"null"}],"description":"Totals for the same number of days just before the period, or `null` when nothing was sent then."},"daily":{"items":{"$ref":"#/components/schemas/EmailStatDayDoc"},"type":"array","title":"Daily","description":"One entry per day of the period, oldest first, with zeros on days with no activity.","example":[{"bounced":1,"clicked":5,"day":"2026-09-14","delivered":41,"opened":18,"sent":42,"spam":0,"unsubscribed":0}]},"tracked":{"type":"boolean","title":"Tracked","description":"`false` when the template has no `{{unsubscribe_url}}` and recorded no opens or clicks in either period. Its `opened` and `clicked` zeros then mean opens and clicks weren't measured, not that nobody opened it.","example":true}},"type":"object","required":["template_key","totals","previous_totals","daily","tracked"],"title":"TemplateEmailStatsDoc"},"TemplateListingPendingChanges":{"properties":{"has_pending_update":{"type":"boolean","title":"Has Pending Update","description":"Whether an update is waiting for review.","example":true},"has_code_changes":{"type":"boolean","title":"Has Code Changes","description":"Whether the pending update includes a new copy of the app.","example":true},"has_metadata_changes":{"type":"boolean","title":"Has Metadata Changes","description":"Whether the pending update changes listing details.","example":true},"metadata_diff":{"additionalProperties":{"$ref":"#/components/schemas/ListingFieldChange"},"type":"object","title":"Metadata Diff","description":"Each listing detail the update changes, keyed by field name. Empty when it changes none.","example":{"version":{"new":"1.1.0","old":"1.0.0"}}}},"type":"object","required":["has_pending_update","has_code_changes","has_metadata_changes","metadata_diff"],"title":"TemplateListingPendingChanges","description":"What's waiting for review on a template listing."},"TemplateListingUpdateInfo":{"properties":{"has_code_changes":{"type":"boolean","title":"Has Code Changes","description":"Always `true`, because every submission copies the app.","example":true},"has_metadata_changes":{"type":"boolean","title":"Has Metadata Changes","description":"Whether `metadata` changed any listing details.","example":false},"auto_approved":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Auto Approved","description":"`true` when the update was applied right away, which happens for a workspace template. Left out for a public template, whose update waits for review.","example":true}},"type":"object","required":["has_code_changes","has_metadata_changes"],"title":"TemplateListingUpdateInfo","description":"What the submission changed."},"TestDeleted":{"properties":{"success":{"type":"boolean","title":"Success","description":"Always `true`.","example":true}},"type":"object","required":["success"],"title":"TestDeleted","description":"The result of deleting a test."},"TestEmailSent":{"properties":{"sent_to":{"type":"string","title":"Sent To","description":"The address the test went to: the email address of the user the credential belongs to.","example":"dana@acme.com"}},"type":"object","required":["sent_to"],"title":"TestEmailSent"},"TestGenerationCheckpointSaved":{"properties":{"success":{"type":"boolean","title":"Success","description":"Always `true`.","example":true}},"type":"object","required":["success"],"title":"TestGenerationCheckpointSaved","description":"The result of saving a generation checkpoint."},"TestModeResponse":{"properties":{"test_mode":{"type":"boolean","title":"Test Mode","description":"Whether the checkout is in test mode (`true`) or live (`false`).","example":false}},"type":"object","required":["test_mode"],"title":"TestModeResponse","description":"The app's Wix Payments mode."},"TestRun":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the run.","example":"68a1c9b7f0b3d9001a7e5d04"},"app_id":{"type":"string","title":"App Id","description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"},"flow_id":{"type":"string","title":"Flow Id","description":"ID of the test this run belongs to.","example":"68a1c2e4f0b3d9001a7e5c21"},"flow_goal":{"type":"string","title":"Flow Goal","description":"The test's goal when the run started.","example":"Sign up, create a project called Launch, and check that it appears on the dashboard."},"flow_role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flow Role","description":"Role the run signs in as, including one Base44 picked from the test's name or goal. Set once the run starts. `null` when the run uses no role.","example":"admin"},"site_url":{"type":"string","title":"Site Url","description":"URL the run opened.","example":"https://preview-6820f3a4e7b91d003c45a1f2.base44.app/?_preview_token=FH-j7wHS7IR_fC1tUh4wCd_fNV1XGC479cuqNSYi9mA"},"status":{"type":"string","enum":["pending","running","analyzing","success","failed","timeout","cancelled","paused"],"title":"Status","description":"Where the run is. `pending`, `running` and `analyzing` mean it's still going. `success` means the browser session finished, and `failed` means it didn't. `timeout`, `cancelled` and `paused` mean the run stopped early. The test passed when `goal_accomplished` is `true` and `failure_reason` isn't `platform` or `internal`.","example":"success"},"actions":{"items":{"$ref":"#/components/schemas/TestRunStep"},"type":"array","title":"Actions","description":"Steps the agent took. Empty until the run finishes.","example":[{"step_summary":"Clicked Sign up in the header to reach the registration form.","step_title":"Opened the sign-up form"}]},"result_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Result Message","description":"The agent's account of how the run went, or `null` before the run finishes.","example":"Created the project and found it on the dashboard."},"goal_accomplished":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Goal Accomplished","description":"Whether the agent accomplished the goal, or `null` before the run finishes. Can be `true` on a run whose `failure_reason` is `platform` or `internal`, which counts as a technical failure.","example":true},"live_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Live Status","description":"Short progress message for display, or `null` before the run starts.","example":"Analyzing results"},"analysis_status":{"anyOf":[{"type":"string","enum":["pending","completed","failed"]},{"type":"null"}],"title":"Analysis Status","description":"Progress of the run's report. `completed` means [Get test run report](/api-reference/get-test-run-report) has it. `null` before the run reaches analysis.","example":"completed"},"failure_reason":{"anyOf":[{"type":"string","enum":["app","platform","internal","out_of_credits"]},{"type":"null"}],"title":"Failure Reason","description":"Why the run didn't pass, or `null` when no reason was recorded. `app` is a problem in your app. `platform` and `internal` are problems on Base44's side, so rerun the test. `out_of_credits` comes with status `paused`.","example":"app"},"credits_charged":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Credits Charged","description":"Credits the run cost, or `null` before it's charged.","example":1.5},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At","description":"When the browser session started, as an ISO 8601 UTC timestamp, or `null` while pending.","example":"2026-09-28T10:16:02+00:00"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At","description":"When the run ended, as an ISO 8601 UTC timestamp, or `null` while it's still going.","example":"2026-09-28T10:18:40+00:00"},"created_date":{"type":"string","title":"Created Date","description":"When the run was requested, as an ISO 8601 UTC timestamp.","example":"2026-09-28T10:16:00+00:00"},"updated_date":{"type":"string","title":"Updated Date","description":"When the run last changed, as an ISO 8601 UTC timestamp.","example":"2026-09-28T10:18:40+00:00"}},"type":"object","required":["id","app_id","flow_id","flow_goal","flow_role","site_url","status","actions","result_message","goal_accomplished","live_status","analysis_status","failure_reason","credits_charged","started_at","completed_at","created_date","updated_date"],"title":"TestRun","description":"One run of a test."},"TestRunIssue":{"properties":{"title":{"type":"string","title":"Title","description":"Short name of the problem.","example":"Project doesn't appear after creation"},"summary":{"type":"string","title":"Summary","description":"What went wrong, as seen from the app's user.","example":"After saving, the dashboard still shows no projects until the page is reloaded."},"severity":{"type":"string","title":"Severity","description":"How serious the problem is. Usually `critical`, `warning` or `info`.","example":"warning"},"category":{"type":"string","title":"Category","description":"Kind of problem. Usually `ui_interaction`, `form_validation`, `navigation`, `content`, `performance`, `accessibility` or `functionality`. Base44 also adds advisory issues with `seed_data_gap`, `auth_required` or `permission_gap` when the test data, sign-in, or the test's role got in the way. Those describe the test setup, not a bug in your app.","example":"functionality"},"related_step_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Related Step Number","description":"Step in the run's `actions` where the problem showed, counting from 1, or `null`.","example":3}},"type":"object","required":["title","summary","severity","category","related_step_number"],"title":"TestRunIssue","description":"A problem the testing agent found during a run."},"TestRunReport":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the report.","example":"68a1ca02f0b3d9001a7e5d11"},"app_id":{"type":"string","title":"App Id","description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"},"flow_id":{"type":"string","title":"Flow Id","description":"ID of the test.","example":"68a1c2e4f0b3d9001a7e5c21"},"execution_id":{"type":"string","title":"Execution Id","description":"ID of the run.","example":"68a1c9b7f0b3d9001a7e5d04"},"flow_goal":{"type":"string","title":"Flow Goal","description":"The test's goal when the run started.","example":"Sign up, create a project called Launch, and check that it appears on the dashboard."},"status":{"type":"string","enum":["goal_accomplished","goal_not_accomplished","failed","timeout"],"title":"Status","description":"The verdict. `failed` and `timeout` mean the report couldn't reach one, and `summary` says what happened.","example":"goal_accomplished"},"summary":{"type":"string","title":"Summary","description":"Plain-language summary of the run.","example":"The project was created and listed on the dashboard, but only after a reload."},"issues":{"items":{"$ref":"#/components/schemas/TestRunIssue"},"type":"array","title":"Issues","description":"Problems found during the run. Empty when there were none.","example":[{"category":"functionality","related_step_number":3,"severity":"warning","summary":"After saving, the dashboard still shows no projects until the page is reloaded.","title":"Project doesn't appear after creation"}]},"created_date":{"type":"string","title":"Created Date","description":"When the report was written, as an ISO 8601 UTC timestamp.","example":"2026-09-28T10:18:39+00:00"},"updated_date":{"type":"string","title":"Updated Date","description":"When the report last changed, as an ISO 8601 UTC timestamp.","example":"2026-09-28T10:18:39+00:00"}},"type":"object","required":["id","app_id","flow_id","execution_id","flow_goal","status","summary","issues","created_date","updated_date"],"title":"TestRunReport","description":"The report for a finished run."},"TestRunStep":{"properties":{"step_title":{"type":"string","title":"Step Title","description":"Short description of the step.","example":"Opened the sign-up form"},"step_summary":{"type":"string","title":"Step Summary","description":"What the agent did in this step and why.","example":"Clicked Sign up in the header to reach the registration form."}},"type":"object","required":["step_title","step_summary"],"title":"TestRunStep","description":"One step the testing agent took during a run."},"TestSuggestion":{"properties":{"name":{"type":"string","title":"Name","description":"Short name for the test.","example":"Filter orders by status"},"goal":{"type":"string","title":"Goal","description":"What the testing agent would try to do, in plain language.","example":"Open the orders page, filter by Shipped, and check that only shipped orders are listed."},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"Role the test would run as, or `null` for a regular user.","example":"admin"}},"type":"object","required":["name","goal","role"],"title":"TestSuggestion","description":"A test Base44 suggests. It isn't saved."},"TestSuggestions":{"properties":{"flows":{"items":{"$ref":"#/components/schemas/TestSuggestion"},"type":"array","title":"Flows","description":"Up to 5 suggested tests. None of them is saved.","example":[{"goal":"Open the orders page, filter by Shipped, and check that only shipped orders are listed.","name":"Filter orders by status","role":"admin"}]}},"type":"object","required":["flows"],"title":"TestSuggestions","description":"Tests Base44 suggests for the app."},"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."},"ToggleReactionPayload":{"properties":{"emoji":{"type":"string","maxLength":16,"minLength":1,"title":"Emoji","description":"Emoji to toggle, up to 16 characters.","example":"👍"}},"type":"object","required":["emoji"],"title":"ToggleReactionPayload","description":"The emoji reaction to toggle."},"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. This is `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"},"ToggleSyncRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled","description":"Whether to turn automatic sync on or off. Send `true` to turn it on and `false` to turn it off.","example":false}},"type":"object","required":["enabled"],"title":"ToggleSyncRequest","description":"The automatic sync setting to apply."},"ToggleSyncResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Always `true`. A change that can't be applied is rejected with an error status instead.","example":true},"sync_enabled":{"type":"boolean","title":"Sync Enabled","description":"Whether automatic sync is now on. The value is `true` when it's on and `false` when it's off, and it always matches what you sent.","example":false}},"type":"object","required":["success","sync_enabled"],"title":"ToggleSyncResponse"},"TopCustomer":{"properties":{"customer_id":{"type":"string","title":"Customer Id","description":"Payment provider customer ID.","example":"cus_T7mK2p9Q4r6S8v"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Customer name from Stripe, or `null` when enrichment is unavailable. Defaults to `null`.","example":"Jane Doe"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Customer email from Stripe, or `null` when enrichment is unavailable. Defaults to `null`.","example":"jane@example.com"},"transaction_count":{"type":"integer","title":"Transaction Count","description":"Number of payments in the selected window. Defaults to 0.","default":0,"example":4},"total_amount":{"type":"integer","title":"Total Amount","description":"Total payment amount in the currencies' smallest units. Filter to one currency before interpreting this value. Defaults to 0.","default":0,"example":10000}},"type":"object","required":["customer_id"],"title":"TopCustomer","description":"A top customer with metrics and optional provider details."},"TopCustomersResponse":{"properties":{"customers":{"items":{"$ref":"#/components/schemas/TopCustomer"},"type":"array","title":"Customers","description":"Customers ranked by the requested metric, highest first. Empty when no matching payments are found.","example":[{"customer_id":"cus_T7mK2p9Q4r6S8v","email":"jane@example.com","name":"Jane Doe","total_amount":10000,"transaction_count":4}]}},"type":"object","required":["customers"],"title":"TopCustomersResponse","description":"Response with top customers list."},"TrackingFixPromptResponse":{"properties":{"composer_prompt":{"type":"string","title":"Composer Prompt","description":"The instruction to send to [Send chat message](/api-reference/send-chat-message). It ends with a reminder to publish, because Google Ads only records conversions from the published app. The value is an empty string when `skipped_reason` is set.","example":"Wire up Google Ads conversion tracking for this app.\n\nCall get_capability_guide(\"google_ads\") first, then follow it."},"conversion_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversion Id","description":"The gtag conversion ID baked into the prompt, or an empty string when the prompt tells the AI to look each one up instead of carrying a concrete ID. Absent when `skipped_reason` is set.","example":"AW-123456789"},"actions_created_or_existing":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Actions Created Or Existing","description":"How many conversion goals the account has after this call, counting the ones it already had. Absent when `skipped_reason` is set.","example":10},"wired_mappings":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Wired Mappings","description":"How many of the account's conversion mappings the prompt carries wiring guidance for. When you send `wire_events` and the account has no enabled mapping, this reports the number of conversion categories the prompt offers instead, so read any value above `0` as a sign that the prompt carries wiring guidance rather than as a mapping count. Absent when `skipped_reason` is set.","example":3},"skipped_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Skipped Reason","description":"Why no prompt was produced. The only value is `no_google_ads_account`, which means the app has no Google Ads account that can carry conversion tracking. The field is absent whenever a prompt was produced.","example":"no_google_ads_account"}},"type":"object","required":["composer_prompt"],"title":"TrackingFixPromptResponse","description":"A ready-made instruction for the AI to install conversion tracking."},"TrackingGap":{"properties":{"id":{"type":"string","title":"Id","description":"Identifier for this gap within the scan. A gap whose ID starts with `missing-call-` is a mapped goal with no firing code, `no-action-` is a mapping with no matching goal in Google Ads, `disabled-` is a disabled mapping, and `orphan-call-` is code firing a conversion no mapping points at.","example":"missing-call-68b1c0d4e7b91d003c45a1f7"},"severity":{"type":"string","title":"Severity","description":"How serious the gap is. `error` means a mapped conversion cannot fire, and `warning` means the setup works but something is configured in a way you probably did not intend.","example":"error"},"message":{"type":"string","title":"Message","description":"What is wrong, written for an engineer. It names the `send_to` and the call shape where those apply.","example":"Order.create is mapped to PURCHASE but no gtag('event', 'conversion', { send_to: 'AW-123456789/AbCdEfGhIj', ... }) call exists in the app code."},"suggested_files":{"items":{"type":"string"},"type":"array","title":"Suggested Files","description":"Up to three files in the app that look like the place to add or fix the call. Empty when the scan has no suggestion.","example":["src/pages/Checkout.jsx"]},"composer_prompt":{"type":"string","title":"Composer Prompt","description":"An instruction you can send to [Send chat message](/api-reference/send-chat-message) to have the AI fix this gap. Only a `missing-call-` gap carries one; it is an empty string on every other kind.","example":"Add a Google Ads conversion call to the order confirmation flow."},"mapping_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mapping Id","description":"The conversion mapping this gap is about. Absent on an `orphan-call-` gap, which is about code rather than a mapping.","example":"68b1c0d4e7b91d003c45a1f7"},"conversion_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversion Type","description":"Conversion category the mapping names, such as `PURCHASE`. Absent on an `orphan-call-` gap.","example":"PURCHASE"},"entity_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Name","description":"App entity the mapping is tied to. Absent on a `disabled-` or `orphan-call-` gap, and empty on a mapping with no entity.","example":"Order"},"trigger_action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Trigger Action","description":"Which write on the entity the mapping covers. Absent on a `disabled-` or `orphan-call-` gap.","example":"create"},"send_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Send To","description":"The gtag `send_to` value the code should fire. Present only on a `missing-call-` gap, which is the only kind that has resolved one.","example":"AW-123456789/AbCdEfGhIj"},"display_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Message","description":"The same problem in plain language, safe to show someone. Present only on a `missing-call-` gap.","example":"Your Purchase conversion is not being recorded when an order is created."}},"type":"object","required":["id","severity","message","suggested_files","composer_prompt"],"title":"TrackingGap","description":"One problem the tracking scan found."},"TrackingScanResult":{"properties":{"gaps":{"items":{"$ref":"#/components/schemas/TrackingGap"},"type":"array","title":"Gaps","description":"Everything the scan flagged. Empty when it found nothing and when it did not run, which `skipped_reason` tells apart.","example":[]},"mappings_count":{"type":"integer","title":"Mappings Count","description":"How many conversion mappings the account has, enabled or not.","example":3},"calls_count":{"type":"integer","title":"Calls Count","description":"How many gtag conversion calls the scan found in the app's code. See the warning on this operation before reading it as zero.","example":0},"is_clean":{"type":"boolean","title":"Is Clean","description":"Whether the scan flagged no error-severity gap (`true`) or at least one (`false`). It also reads `true` when the scan did not run at all, so check `skipped_reason` first.","example":false},"enabled_mappings_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Enabled Mappings Count","description":"How many of the mappings are enabled. Absent when `skipped_reason` is set.","example":2},"skipped_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Skipped Reason","description":"Why nothing was scanned. The value is `no_google_ads_account` when the app has no active Google Ads account, or `app_not_found`. The field is absent whenever a scan ran.","example":"no_google_ads_account"}},"type":"object","required":["gaps","mappings_count","calls_count","is_clean"],"title":"TrackingScanResult","description":"What the conversion-tracking scan found."},"TrackingScriptResponse":{"properties":{"script":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Script","description":"The gtag snippet, wrapped in a `<script>` tag and ready to place in the app's HTML, or `null` when no goal on the account has a published tag yet. [Build Google Ads tracking fix prompt](/api-reference/build-google-ads-tracking-fix-prompt) creates the goals, and Google takes a few minutes to publish each tag, so a `null` straight after setup means you should read this again shortly.","example":"<script>\nwindow.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', 'AW-123456789', {'send_page_view': false});\n</script>"}},"type":"object","required":["script"],"title":"TrackingScriptResponse","description":"The gtag snippet that reports the app's conversions to Google Ads."},"TransactionAddress":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Full name, or `null` when not provided.","example":"Jane Doe"},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address","description":"Street address, or `null` when not provided.","example":"500 Terry Francine St"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City","description":"City, or `null` when not provided.","example":"San Francisco"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"State or region, or `null` when not provided.","example":"CA"},"postal_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Postal Code","description":"Postal code, or `null` when not provided.","example":"94158"},"country_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country Code","description":"Two-letter country code, or `null` when not provided.","example":"US"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Email address, or `null` when not provided.","example":"jane@example.com"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Phone number, or `null` when not provided.","example":"+14155550123"}},"type":"object","title":"TransactionAddress","description":"Billing or shipping details. Each provider fills a different subset of the fields."},"TransactionDetails":{"properties":{"transaction_id":{"type":"string","title":"Transaction Id","description":"Provider ID of the payment. For Stripe this is the charge, which can differ from the ID you looked it up by.","example":"ch_T7mK2p9Q4r6S8v"},"status":{"type":"string","title":"Status","description":"Where the payment stands. Either `succeeded`, `pending`, `failed`, `refunded`, `partially_refunded`, `pending_refund`, or `chargeback`. Other provider values come through in lowercase.","default":"","example":"partially_refunded"},"payment_type":{"type":"string","title":"Payment Type","description":"Either `one_time` or `recurring`, or an empty string when the provider doesn't say.","default":"","example":"one_time"},"created_date":{"type":"string","title":"Created Date","description":"When the payment was made, as an ISO 8601 timestamp. Empty when the provider doesn't say.","default":"","example":"2026-08-25T10:00:00Z"},"currency":{"type":"string","title":"Currency","description":"Three-letter ISO 4217 code of the payment's currency, in uppercase. Defaults to `USD`.","default":"USD","example":"USD"},"amount":{"type":"integer","title":"Amount","description":"Amount paid, in the smallest unit of the payment's currency.","default":0,"example":2500},"fee":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Fee","description":"Processing fee the provider took, in the smallest unit of the payment's currency, or `null` when none was taken, as on a declined payment.","example":103},"net":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Net","description":"What the merchant kept after the fee, in the smallest unit of the payment's currency, or `null` when no money moved.","example":2397},"service_fee":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Service Fee","description":"Application fee taken on top of the processing fee, in the smallest unit of the payment's currency, or `null` when there is none.","example":50},"refunded_amount":{"type":"integer","title":"Refunded Amount","description":"Total refunded so far, in the smallest unit of the payment's currency.","default":0,"example":1000},"refunds":{"items":{"$ref":"#/components/schemas/TransactionRefund"},"type":"array","title":"Refunds","description":"Refunds against the payment. Empty when there are none.","default":[],"example":[{"amount":1000,"created_date":"2026-08-26T09:15:00Z","id":"re_T7mK2p9Q4r6S8v","status":"succeeded"}]},"method_kind":{"type":"string","title":"Method Kind","description":"Payment method type as the provider names it, for example `card`. Empty when the provider doesn't say.","default":"","example":"card"},"method_moto":{"type":"boolean","title":"Method Moto","description":"`true` when the merchant entered the card for the customer, as in a phone or mail order, and `false` otherwise. Always `false` for Stripe.","default":false,"example":false},"card_network":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Card Network","description":"Card network, or `null` when the payment wasn't by card.","example":"visa"},"card_masked":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Card Masked","description":"Masked card number, or `null` when the payment wasn't by card. Stripe gives only the last four digits.","example":"4242"},"card_holder":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Card Holder","description":"Cardholder name, or `null` when the provider doesn't have it.","example":"Jane Doe"},"provider_id":{"type":"string","title":"Provider Id","description":"Processor that handled the payment. Always `stripe` for Stripe. For Wix, the processor Wix routed the payment through.","default":"","example":"stripe"},"merchant_account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Merchant Account Id","description":"Merchant account at the processor, or `null` when unknown. Always `null` for Stripe.","example":"4f1c2a7e-9b3d-4e8f-a6c5-2d7e9f0b1a3c"},"display_order_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Order Id","description":"Order number shown to the customer, or `null` when there is none. Always `null` for Stripe.","example":"10042"},"items":{"items":{"$ref":"#/components/schemas/TransactionOrderItem"},"type":"array","title":"Items","description":"Items bought. Empty when the provider doesn't list them.","default":[],"example":[{"amount":2500,"name":"Oak side table","quantity":1}]},"billing":{"anyOf":[{"$ref":"#/components/schemas/TransactionAddress"},{"type":"null"}],"description":"Billing details the customer gave, or `null` when there are none.","example":{"address":"500 Terry Francine St","city":"San Francisco","country_code":"US","email":"jane@example.com","name":"Jane Doe","phone":"+14155550123","postal_code":"94158","state":"CA"}},"shipping":{"anyOf":[{"$ref":"#/components/schemas/TransactionAddress"},{"type":"null"}],"description":"Shipping details, or `null` when the payment has none.","example":{"address":"500 Terry Francine St","city":"San Francisco","country_code":"US","name":"Jane Doe","phone":"+14155550123","postal_code":"94158","state":"CA"}},"disputes":{"items":{"$ref":"#/components/schemas/TransactionDispute"},"type":"array","title":"Disputes","description":"Disputes against the payment. Empty when there are none.","default":[],"example":[]},"history":{"items":{"$ref":"#/components/schemas/TransactionHistoryEvent"},"type":"array","title":"History","description":"Timeline of the payment, its refunds, and its disputes, newest first.","default":[],"example":[{"date":"2026-08-26T09:15:00Z","kind":"refund","lines":[{"amount":1000,"label":"refunded_amount"}],"status":"succeeded"},{"date":"2026-08-25T10:00:00Z","kind":"payment","lines":[{"amount":2500,"label":"amount"},{"amount":-103,"label":"processing_fee"},{"amount":2397,"label":"net"}],"status":"succeeded"}]},"decline_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decline Reason","description":"Why the payment was declined, as the provider words it, or `null` when it wasn't declined.","example":"Your card has insufficient funds."}},"type":"object","required":["transaction_id"],"title":"TransactionDetails","description":"One payment in full, as its payment provider reports it."},"TransactionDispute":{"properties":{"id":{"type":"string","title":"Id","description":"Provider ID of the dispute.","default":"","example":"dp_T7mK2p9Q4r6S8v"},"state":{"type":"string","title":"State","description":"Where the dispute stands. Either `open`, `won`, `lost`, or `accepted`. An `open` dispute needs a response by `deadline`. Other provider values come through in lowercase.","default":"","example":"open"},"amount":{"type":"integer","title":"Amount","description":"Amount disputed, in the smallest unit of the payment's currency.","default":0,"example":2500},"fee":{"type":"integer","title":"Fee","description":"Processing fee returned with the dispute, in the smallest unit of the payment's currency. Always 0 for Stripe.","default":0,"example":0},"chargeback_fee":{"type":"integer","title":"Chargeback Fee","description":"Fee charged for the dispute, in the smallest unit of the payment's currency. Always 0 for Stripe.","default":0,"example":1500},"deadline":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deadline","description":"Deadline to respond to the dispute, as an ISO 8601 timestamp, or `null` when there is none.","example":"2026-09-08T23:59:59Z"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Reason the provider gives for the dispute, or `null` when it gives none.","example":"fraudulent"},"created_date":{"type":"string","title":"Created Date","description":"When the dispute was opened, as an ISO 8601 timestamp. Empty when the provider doesn't say.","default":"","example":"2026-08-28T14:02:11Z"}},"type":"object","title":"TransactionDispute","description":"One dispute against the payment."},"TransactionHistoryEvent":{"properties":{"date":{"type":"string","title":"Date","description":"When it happened, as an ISO 8601 timestamp. Empty when the provider doesn't say.","default":"","example":"2026-08-25T10:00:00Z"},"kind":{"type":"string","title":"Kind","description":"What happened. Either `payment`, `refund`, or `chargeback`.","default":"","example":"payment"},"status":{"type":"string","title":"Status","description":"Status of that step, using the same values as the matching payment, refund, or dispute status.","default":"","example":"succeeded"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Text from the provider, such as a decline reason or dispute reason, or `null` when there is none.","example":"Your card has insufficient funds."},"lines":{"items":{"$ref":"#/components/schemas/TransactionHistoryLine"},"type":"array","title":"Lines","description":"Money lines for this entry, such as the amount, fee, and net.","default":[],"example":[{"amount":2500,"label":"amount"},{"amount":-103,"label":"processing_fee"},{"amount":2397,"label":"net"}]}},"type":"object","title":"TransactionHistoryEvent","description":"One entry in the payment's timeline."},"TransactionHistoryLine":{"properties":{"label":{"type":"string","title":"Label","description":"What the line is. Either `amount`, `processing_fee`, `net`, `refunded_amount`, `chargeback_amount`, or `chargeback_fee`.","example":"processing_fee"},"amount":{"type":"integer","title":"Amount","description":"Amount of the line, in the smallest unit of the payment's currency. Money that left the merchant, such as a fee, is negative.","example":-103}},"type":"object","required":["label","amount"],"title":"TransactionHistoryLine","description":"One money line under a timeline entry."},"TransactionOrderItem":{"properties":{"name":{"type":"string","title":"Name","description":"Name of the item. Empty when the provider doesn't say.","default":"","example":"Oak side table"},"quantity":{"type":"integer","title":"Quantity","description":"Number of units bought. Defaults to 1.","default":1,"example":1},"amount":{"type":"integer","title":"Amount","description":"Price of one unit, in the smallest unit of the payment's currency, rather than the line total.","default":0,"example":2500}},"type":"object","title":"TransactionOrderItem","description":"A purchased line item."},"TransactionRefund":{"properties":{"id":{"type":"string","title":"Id","description":"Provider ID of the refund.","default":"","example":"re_T7mK2p9Q4r6S8v"},"amount":{"type":"integer","title":"Amount","description":"Amount refunded, in the smallest unit of the payment's currency.","default":0,"example":1000},"status":{"type":"string","title":"Status","description":"Where the refund stands. Either `succeeded`, `pending`, or `failed`. Other provider values come through in lowercase.","default":"","example":"succeeded"},"arn":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Arn","description":"Acquirer reference number, which the customer's bank can use to trace the refund, or `null` until one is assigned. Always `null` for Stripe.","example":"74537606238000012345678"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Private note stored with the refund, or `null` when there is none. Always `null` for Stripe.","example":"Customer returned one item."},"created_date":{"type":"string","title":"Created Date","description":"When the refund was created, as an ISO 8601 timestamp. Empty when the provider doesn't say.","default":"","example":"2026-08-26T09:15:00Z"}},"type":"object","title":"TransactionRefund","description":"One refund against the payment."},"TransferToWorkspaceMemberRequest":{"properties":{"new_owner_user_id":{"type":"string","minLength":1,"title":"New Owner User Id","description":"ID of the Base44 user who becomes the owner. They have to be an owner, admin, editor, or member of the app's workspace.","example":"6706af53b9c1e2004a37d85f"},"keep_sender_as_collaborator":{"type":"boolean","title":"Keep Sender As Collaborator","description":"Whether the app's current owner keeps editor access to the app after the transfer (`true`) or loses their direct access to it (`false`). An owner or admin of the workspace the app ends up in keeps access through that workspace either way. Defaults to `false`.","default":false,"example":false}},"type":"object","required":["new_owner_user_id"],"title":"TransferToWorkspaceMemberRequest","description":"The workspace member who becomes the app's owner."},"TriggerManualRunConfig":{"properties":{"requires_previous_run":{"type":"boolean","title":"Requires Previous Run","description":"Whether a manual run must replay a previous run's payload, because the trigger's own payload references real in-app state a synthetic one can't stand in for. This is `false` for triggers that run without a payload, for example a scheduled trigger.","default":true,"example":false}},"type":"object","title":"TriggerManualRunConfig","description":"Per-trigger behavior of the app editor'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."},"UnpublishedApp":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"},"name":{"type":"string","title":"Name","description":"Display name of the app.","example":"My CRM"},"is_unpublished":{"type":"boolean","title":"Is Unpublished","description":"Whether the app is offline. Always `true` here, because the unpublish succeeded.","example":true},"last_deployed_at":{"type":"string","format":"date-time","title":"Last Deployed At","description":"Time the app was last published, as a UTC timestamp in ISO 8601 format. Unpublishing doesn't change it.","example":"2026-08-02T14:30:00"}},"type":"object","required":["id","name","is_unpublished","last_deployed_at"],"title":"UnpublishedApp","description":"The app, now offline."},"UpdateAccessRequestPayload":{"properties":{"data":{"additionalProperties":true,"type":"object","title":"Data","description":"Custom `User` fields to set on the invitation, as field names and values.","example":{"department":"sales","team":"EMEA"}}},"type":"object","required":["data"],"title":"UpdateAccessRequestPayload"},"UpdateAnchorPayload":{"properties":{"anchor":{"$ref":"#/components/schemas/CommentAnchor","description":"The thread's new pin. It replaces the old one in full."},"screenshot_file_uri":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Screenshot File Uri","description":"`file_uri` of a screenshot uploaded to this app with [Upload app file](/api-reference/upload-app-file) and `visibility` set to `private`. Starts with `mp/private/` followed by the app's ID.","example":"mp/private/6820f3a4e7b91d003c45a1f2/4b1e9c2a7_shot.png"}},"type":"object","required":["anchor"],"title":"UpdateAnchorPayload","description":"A thread's new pin and screenshot."},"UpdateAppFolderRequest":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name","description":"New name, 1 to 100 characters.","example":"Client work"},"position":{"type":"number","title":"Position","description":"New sort key among folders. Lower values come first.","example":3.0},"color":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Color","description":"New color, up to 32 characters, or `null` to clear it.","example":"#10B981"},"icon":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Icon","description":"New icon name, up to 64 characters, or `null` to clear it.","example":"folder"}},"type":"object","title":"UpdateAppFolderRequest"},"UpdateConnectionToggleRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled","description":"Whether to turn the switch on (`true`) or off (`false`).","example":false}},"type":"object","required":["enabled"],"title":"UpdateConnectionToggleRequest","description":"The new value of the switch."},"UpdateDefaultCreditLimitRequest":{"properties":{"default_credit_limit":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Default Credit Limit","description":"Most credits each member without their own limit can use in the workspace each month, at least 1. `null`, or leaving it out, removes the default.","example":300}},"type":"object","title":"UpdateDefaultCreditLimitRequest","description":"The workspace's default member credit limit."},"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"},"UpdateFlowRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":100,"minLength":1},{"type":"null"}],"title":"Name","description":"New name for the test.","example":"Create a project"},"goal":{"anyOf":[{"type":"string","maxLength":500,"minLength":1},{"type":"null"}],"title":"Goal","description":"New goal for the test, in plain language.","example":"Sign up, create a project called Launch, and check that it appears on the dashboard."},"role":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Role","description":"Role to run the test as. Send `null` to clear it, so Base44 picks one from the name or goal again.","example":"admin"}},"type":"object","title":"UpdateFlowRequest"},"UpdateGroupRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":100,"minLength":1},{"type":"null"}],"title":"Name","description":"New name, up to 100 characters. An identity-provider group's name can't change.","example":"Brand design"},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Description","description":"New description, up to 500 characters. An empty string clears it.","example":"Brand and marketing designers"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"New role: `admin`, `editor`, `viewer`, or `no_access`. Send `null` to clear it so the group grants no role, or leave it out to keep it.","example":"viewer"}},"type":"object","title":"UpdateGroupRequest"},"UpdateMemberCreditLimitRequest":{"properties":{"credit_limit":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Credit Limit","description":"Most credits the member can use in the workspace each month, at least 1. `null`, or leaving it out, removes their own limit, so the workspace default applies.","example":500}},"type":"object","title":"UpdateMemberCreditLimitRequest","description":"A member's credit limit."},"UpdateMemberRoleRequest":{"properties":{"role":{"type":"string","enum":["admin","editor","viewer"],"description":"The member's new role. `admin` needs the Business plan or higher.","example":"editor"}},"type":"object","required":["role"],"title":"UpdateMemberRoleRequest","description":"A member's new role."},"UpdateOrganizationConnectorRequest":{"properties":{"name":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Name","description":"New name. Must be unique in the workspace, matching case exactly.","example":"Sales Google Calendar"},"credential_source":{"anyOf":[{"type":"string","enum":["byo","base44"]},{"type":"null"}],"description":"Send `base44` to move a connector from your own OAuth app onto Base44's. It can't move back. Moving drops the stored client ID and secret, and app users who connected through your OAuth app have to connect again.","example":"base44"},"client_id":{"anyOf":[{"type":"string","maxLength":512,"minLength":1},{"type":"null"}],"title":"Client Id","description":"New client ID of your OAuth app. Not accepted for a connector on Base44's OAuth app.","example":"1234567890-abc123def456.apps.googleusercontent.com"},"client_secret":{"anyOf":[{"type":"string","maxLength":512,"minLength":1},{"type":"null"}],"title":"Client Secret","description":"New client secret of your OAuth app, to rotate it. Not accepted for a connector on Base44's OAuth app. It's stored encrypted and never returned.","example":"your-new-oauth-client-secret"},"scopes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Scopes","description":"New list of OAuth scopes, replacing the current one.","example":["https://www.googleapis.com/auth/calendar"]},"connection_config":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Connection Config","description":"New connection values, replacing the current ones.","example":{}},"uses_oauth_gateway":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Uses Oauth Gateway","description":"Whether new connections return to the single gateway redirect URI (`true`) or to the per-app ones (`false`). Register the matching URIs in the provider's console first, from [Get OAuth redirect URIs](/api-reference/get-oauth-redirect-uris). Not accepted for a connector on Base44's OAuth app, which always uses the gateway.","example":true}},"type":"object","title":"UpdateOrganizationConnectorRequest","description":"The workspace connector fields to change. Leave out the ones to keep."},"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"},"UpdatePriceRequest":{"properties":{"active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Active","description":"Whether the price is active. Leave it out to keep the current value. Sending `null` has the same effect. Defaults to `null`.","example":false},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Metadata to store, as string key-value pairs. [Stripe's metadata rules](https://docs.stripe.com/api/metadata) limit it to 50 keys, 40 characters per key, and 500 characters per value. Defaults to `null`. Leave it out to keep the current value. Sending `null` has the same effect.","example":{"tier":"standard"}},"nickname":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nickname","description":"New internal price label. Leave it out to keep the current value. Sending `null` has the same effect. Defaults to `null`.","example":"Monthly membership"}},"type":"object","title":"UpdatePriceRequest","description":"Request to update a Stripe price."},"UpdateProductRequest":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"New product name. Leave it out to keep the current value. Sending `null` has the same effect. Defaults to `null`.","example":"Studio membership"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"New product description. Leave it out to keep the current value. Sending `null` has the same effect. Defaults to `null`.","example":"Access to weekly classes."},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Metadata to store, as string key-value pairs. [Stripe's metadata rules](https://docs.stripe.com/api/metadata) limit it to 50 keys, 40 characters per key, and 500 characters per value. Defaults to `null`. Leave it out to keep the current value. Sending `null` has the same effect.","example":{"tier":"standard"}},"active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Active","description":"Whether the product is active. Leave it out to keep the current value. Sending `null` has the same effect. Defaults to `null`.","example":false}},"type":"object","title":"UpdateProductRequest","description":"Request to update a Stripe product."},"UpdateShareRequest":{"properties":{"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"App role the group's members get, one of the roles defined on the app's User entity. Send `null`, or leave `role` out, to stop assigning a role, so members get the default `user` role.","example":"admin"}},"type":"object","title":"UpdateShareRequest"},"UpdateShareResponse":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the share.","example":"68d1c2e4a9b7f3001e5c8a41"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"App role the share now gives the group's members, or `null` when it assigns none.","example":"admin"}},"type":"object","required":["id","role"],"title":"UpdateShareResponse"},"UpdateSkillRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":64,"minLength":1},{"type":"null"}],"title":"Name","description":"New name. Name of the skill, up to 64 characters: lowercase letters and digits in words joined by single hyphens, such as `code-review`. Unique within the workspace.","example":"brand-voice"},"description":{"anyOf":[{"type":"string","maxLength":1024,"minLength":1},{"type":"null"}],"title":"Description","description":"New description. When the skill applies, up to 1024 characters. The AI builder reads it to decide when to load the skill, so say what the skill is for.","example":"Use when writing any user-facing copy for our apps."},"body":{"anyOf":[{"type":"string","maxLength":15000,"minLength":1},{"type":"null"}],"title":"Body","description":"New instructions. The instructions the AI builder follows once it loads the skill, up to 15000 characters.","example":"Write in a warm, plain-spoken voice. Use sentence case for headings and keep buttons to two words."},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled","description":"`true` lets the AI builder use the skill, and `false` stops it.","example":false}},"type":"object","title":"UpdateSkillRequest"},"UpdateSlugPayload":{"properties":{"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug","description":"New slug, 3 to 50 characters of letters, numbers, and hyphens that starts and ends with a letter or number. It has to be unused by any other app and not a reserved name, and it's lowercased before it's saved. Send `null` to reset it to the slug Base44 generates from the app's name and ID.","example":"nordwind-furniture"}},"type":"object","required":["slug"],"title":"UpdateSlugPayload"},"UpdateTestModeRequest":{"properties":{"test_mode":{"type":"boolean","title":"Test Mode","description":"Whether to switch the checkout to test mode (`true`) or live (`false`).","example":false}},"additionalProperties":false,"type":"object","required":["test_mode"],"title":"UpdateTestModeRequest"},"UpdateWebhookPayload":{"properties":{"target_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Url","description":"New public HTTPS URL that receives the events.","example":"https://example.com/hooks/agent-v2"},"events":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Events","description":"New set of events to receive. One or more of `message.created` and `message.completed`.","example":["message.created","message.completed"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"New label for the webhook, or `null` to remove it.","example":"CRM sync"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled","description":"`true` turns the webhook back on and resets `consecutive_failures`. `false` turns it off.","example":true},"signing":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signing","description":"`rotate` generates a new signing secret and returns it once in `secret`. `disable` removes the secret, so deliveries are no longer signed.","example":"rotate"}},"type":"object","title":"UpdateWebhookPayload"},"UpdateWorkflowRequest":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"New name. Leave it out to keep the current one. Must be unique among the app's workflows that are not archived, so renaming to one another live workflow already uses is rejected.","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. Leave it out to keep the current one. One that differs from the current definition is saved as a new immutable version, and the workflow runs it from then on. An invalid definition is rejected with the validation errors, and nothing is saved.","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. Leave it out to keep the current one. See [Triggers](/developers/references/apps-api/sections/workflows#triggers) for its shape.","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. This is only recorded when `definition` actually changes, since that's what creates the version it's attached to.","example":"Send to the ops alias instead"}},"type":"object","title":"UpdateWorkflowRequest"},"UpdatedInvitation":{"properties":{"success":{"type":"boolean","title":"Success","description":"Always `true`. An update that doesn't happen returns an error instead.","example":true},"data":{"additionalProperties":true,"type":"object","title":"Data","description":"The invitation's custom `User` fields after the update, limited by the `User` entity's field-level read rules.","example":{"department":"sales","team":"EMEA"}}},"type":"object","required":["success","data"],"title":"UpdatedInvitation","description":"Doc-only: the handler returns a plain dict with exactly these keys."},"UploadAsset":{"properties":{"file":{"type":"string","format":"binary","title":"File","description":"The file to upload. Its name must end in one of the supported extensions."}},"type":"object","required":["file"],"title":"UploadAsset"},"UploadWorkspaceFont":{"properties":{"file":{"type":"string","format":"binary","title":"File","description":"The font file: TrueType, OpenType, WOFF or WOFF2, up to 5 MB."}},"type":"object","required":["file"],"title":"UploadWorkspaceFont"},"UploadedAppFile":{"properties":{"file_uri":{"type":"string","title":"File Uri","description":"Storage URI of the file. `mp/public/` or `mp/private/` shows its visibility.","example":"mp/public/6820f3a4e7b91d003c45a1f2/3f2504e0_logo.png"},"url":{"type":"string","title":"Url","description":"Link to the file. For a public file it's permanent and anyone with it can open the file. For a private file it's a signed link that stops working after `expires_in` seconds.","example":"https://storage.base44.com/6820f3a4e7b91d003c45a1f2/3f2504e0_logo.png"}},"type":"object","required":["file_uri","url"],"title":"UploadedAppFile","description":"Doc-only: the handler returns a plain dict with exactly these keys."},"UpsertPayload":{"properties":{"records":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Records","description":"The records to create or update, up to 500, each a flat JSON object of the fields the entity's schema declares. Every record needs a value for each `key` field.","example":[{"amount":4200,"order_number":"A-1001","status":"paid"},{"amount":1800,"order_number":"A-1002","status":"draft"}]},"key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Key","description":"Field, or list of fields, that identifies a record. A stored record whose values in these fields match a record you send is updated, and otherwise a new record is created.","example":"order_number"}},"type":"object","required":["records","key"],"title":"UpsertPayload"},"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."},"UserApp":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the app.","example":"6820f3a4e7b91d003c45a1f2"},"created_date":{"type":"string","format":"date-time","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":{"type":"string","format":"date-time","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"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By","description":"Email of the user who created the app.","example":"developer@example.com"},"organization_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Id","description":"ID of the workspace the app belongs to.","example":"67e0b12c4d8a3f005b21c9e4"},"app_type":{"type":"string","title":"App Type","description":"Kind of app, set when it's created. One of `user_app` (a web app), `user_agent` (an AI agent), `mobile_app` (a native mobile app), `user_game` (a game), `slide` (a presentation), or `imported_app` (an app imported from an existing code repository). List apps returns it as stored, so an older agent app can report `agent` instead of `user_agent`, and some older apps don't include it. Get app always returns one of the values above.","default":"user_app","example":"user_app"},"name":{"type":"string","title":"Name","description":"Display name of the app.","default":"untitled","example":"My CRM"},"user_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Description","description":"Description of the app, or `null` if none was set. An app created without a `name` gets a generated description once a build turn changes it.","example":"A CRM to track leads and deals"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url","description":"URL of the app's logo, or `null` if it has none. Set it with [Set app logo](/api-reference/set-app-logo) or [Generate app logo](/api-reference/generate-app-logo).","example":"https://storage.base44.com/6820f3a4e7b91d003c45a1f2/3f2504e0_logo.png"},"social_image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Social Image Url","description":"URL of the image shown when the app is shared on social platforms, or `null` if none was set, in which case the logo is shown instead. Set it with [Set social image](/api-reference/set-social-image).","example":"https://storage.base44.com/6820f3a4e7b91d003c45a1f2/7c9e6679_social.png"},"public_settings":{"type":"string","enum":["private_with_login","public_with_login","public_without_login","workspace_with_login"],"title":"Public Settings","description":"Who can open the published app. Each value is described under [Update app](/api-reference/update-app), which also changes it.","default":"public_with_login","example":"public_with_login"},"auth_config":{"$ref":"#/components/schemas/AuthConfiguration","description":"Which login methods the app's login page offers. If the workspace enforces SSO for apps, the login page offers only SSO, whatever these are set to. Change them with [Update app](/api-reference/update-app)."},"hide_entity_created_by":{"type":"boolean","title":"Hide Entity Created By","description":"Whether the app's entity records leave out the email of the user who created each one (`created_by`). Newer apps always leave the email out, whatever this is set to.","default":false,"example":true},"is_remixable":{"type":"boolean","title":"Is Remixable","description":"Whether others can remix the app. While it's `true`, the published app also shows the Base44 badge if `public_settings` is `public_with_login` or `public_without_login`.","default":false,"example":false},"main_branch_protected":{"type":"boolean","title":"Main Branch Protected","description":"Whether the app's main branch is protected, so changes to main must go through a branch that's merged back. Change it with [Set main branch protection](/api-reference/set-main-branch-protection).","default":false,"example":false},"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"},"is_unpublished":{"type":"boolean","title":"Is Unpublished","description":"Whether the app was taken offline with [Unpublish app](/api-reference/unpublish-app) and hasn't been published again since. While it's `true`, the published URL is offline and `last_deployed_at` still shows the last publish.","default":false,"example":false},"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"},"owner_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner Id","description":"ID of the user who owns the app. It starts as the creator and changes when ownership is transferred.","example":"6706af53b9c1e2004a37d85f"},"status":{"$ref":"#/components/schemas/StatusObject","description":"The app's current build status. Poll while a build is in progress to watch it finish. This tracks building, not publishing."},"dev_environment_enabled":{"type":"boolean","title":"Dev Environment Enabled","description":"Whether test data is on, which gives the app a test database next to its live one.","default":false,"example":false}},"type":"object","title":"UserApp"},"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, each with a `code`, a human-readable `message`, and a `location` pointing into the definition. Empty when `valid` is `true`.","example":[{"code":"UNSUPPORTED_TASK_TYPE","location":"do[0].notify","message":"Task 'notify' has no supported type key. Supported: ['call', 'switch', 'wait']"}]},"supported_task_types":{"items":{"type":"string"},"type":"array","title":"Supported Task Types","description":"The fixed set of [task types](/developers/references/apps-api/sections/workflows#tasks-and-activities) a definition's `do` steps can use, `call`, `switch`, or `wait`. This doesn't vary by app.","example":["call","switch","wait"]},"available_activities":{"items":{"type":"string"},"type":"array","title":"Available Activities","description":"The [activities](/developers/references/apps-api/sections/workflows#tasks-and-activities) a `call` task on this app can invoke. This varies by app.","example":["compute_seconds_until","invoke_backend_function"]}},"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"},"VersionHistoryInfo":{"properties":{"state":{"type":"string","enum":["enabled","needs_upgrade","unavailable"],"title":"State","description":"Whether the app has version history. `enabled` means Base44 is keeping history and you can list checkpoints and restore from them. `needs_upgrade` means the app's workspace plan doesn't include version history. `unavailable` means version history doesn't cover this app, for example because its data is stored in the workspace's own database.","example":"enabled"},"retention_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Retention Days","description":"How many days of history Base44 keeps, or `null` unless `state` is `enabled`.","example":30},"history_available_since":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"History Available Since","description":"The earliest moment you can restore to, as a UTC timestamp in ISO 8601 format with a `Z` suffix, when history was paused while the workspace was on a plan without it. It is `null` unless `state` is `enabled`, and also when `retention_days` alone limits how far back you can go.","example":"2026-06-02T08:00:00Z"}},"type":"object","required":["state"],"title":"VersionHistoryInfo","description":"Whether version history is on for an app, and how far back it goes."},"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, as an ISO 8601 UTC timestamp.","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"},"WixDashboardDestination":{"type":"string","enum":["dashboard","payment_settings","payouts"],"title":"WixDashboardDestination","description":"Where in the Wix dashboard a link should land."},"WixPaymentDisconnectResult":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the disconnect completed.","example":true}},"type":"object","required":["success"],"title":"WixPaymentDisconnectResult","description":"The result of disconnecting a Wix payment integration."},"WorkflowListItem":{"properties":{"social_post":{"anyOf":[{"$ref":"#/components/schemas/WorkflowSocialPost"},{"type":"null"}],"description":"Current post content for a social publishing workflow, or `null` when no linked post is available."},"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`. See [Workflow status](/developers/references/apps-api/sections/workflows#workflow-status) for what each means and how it changes.","example":"active"},"status_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Reason","description":"Why Base44 changed the workflow's status on its own, as one of `consecutive_failures`, `end_condition_reached`, `migration_activation_failed`, or `workflows_not_available`. This is `null` when you changed the status yourself. See [Workflow status](/developers/references/apps-api/sections/workflows#workflow-status) for what each code means.","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, as an ISO 8601 UTC timestamp. This is `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 the workflow's most recent run ended: `success`, `failed`, or `cancelled`. This is `null` before the first run. See [Workflow status](/developers/references/apps-api/sections/workflows#workflow-status) for how this compares to a run's own `status`.","example":"success"},"created_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created Date","description":"When the workflow was created, as an ISO 8601 UTC timestamp.","example":"2026-07-02T11:04:00Z"}},"type":"object","required":["id","app_id","name","status"],"title":"WorkflowListItem"},"WorkflowResponse":{"properties":{"social_post":{"anyOf":[{"$ref":"#/components/schemas/WorkflowSocialPost"},{"type":"null"}],"description":"Current post content for a social publishing workflow, or `null` when no linked post is available."},"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. This is `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`. See [Workflow status](/developers/references/apps-api/sections/workflows#workflow-status) for what each means and how it changes.","example":"active"},"status_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Reason","description":"Why Base44 changed the workflow's status on its own, as one of `consecutive_failures`, `end_condition_reached`, `migration_activation_failed`, or `workflows_not_available`. This is `null` when you changed the status yourself. See [Workflow status](/developers/references/apps-api/sections/workflows#workflow-status) for what each code means.","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. This is `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":"Caller-specific context captured when the workflow was created, such as the conversation that authored it. This API never sets it, so a workflow you create through it starts with `null`. Updating a workflow through this API doesn't clear an existing value either, so a workflow originally authored through the Base44 app editor or a superagent keeps its context here even after an API update.","example":{"anchor_message_id":"68b1c0d4e7b91d003c45a1f2","conversation_id":"0195f2a1-4c3e-7b21-9f0d-2a5c8e1b4d77"}},"last_run_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Run At","description":"When the workflow last started running, as an ISO 8601 UTC timestamp. This is `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 the workflow's most recent run ended: `success`, `failed`, or `cancelled`. This is `null` before the first run. See [Workflow status](/developers/references/apps-api/sections/workflows#workflow-status) for how this compares to a run's own `status`.","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, as an ISO 8601 UTC timestamp.","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, as an ISO 8601 UTC timestamp.","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. This is `null` when no version has been saved yet. Only this endpoint returns it. [List workflows](/api-reference/list-workflows) leaves it out.","example":{"do":[],"document":{"dsl":"1.0.0","name":"notify","version":"1.0.0"}}}},"type":"object","required":["id","app_id","name","status"],"title":"WorkflowResponse"},"WorkflowSocialPost":{"properties":{"platform":{"type":"string","title":"Platform","description":"Social network the post will be published to.","example":"linkedin"},"title":{"type":"string","title":"Title","description":"Title of the scheduled post.","example":"Meet our new app"},"body":{"type":"string","title":"Body","description":"Current text of the scheduled post, before its hashtags.","example":"Our new app is ready to try."},"hashtags":{"items":{"type":"string"},"type":"array","title":"Hashtags","description":"Hashtags appended when publishing the post.","example":["launch"]}},"type":"object","required":["platform","title","body","hashtags"],"title":"WorkflowSocialPost"},"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 duration of the runs in the window, in milliseconds, including time spent in `wait` tasks.","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"},"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 AI 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"},"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"},"WorkspaceBrand":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the brand.","example":"68e0c4a1b9d2f7003a5e1b42"},"title":{"type":"string","title":"Title","description":"Title of the brand, from `kit.title`. `Generating…` while the brand is being generated.","example":"Nordwind"},"kit":{"additionalProperties":true,"type":"object","title":"Kit","description":"The brand's identity. It needs a non-empty `title`, which becomes the brand's title, and a `description`. Brands Base44 generates also carry `tagLine`, `values`, `toneOfVoice`, `designGuidelines`, `componentStyle`, and a `logo` with its `url`. Nothing else in it is checked. Empty while the brand is being generated.","example":{"description":"A family furniture workshop that builds solid oak tables and chairs to order.","logo":{"altText":"Nordwind logo","url":"https://media.base44.com/images/public/nordwind-logo.png"},"tagLine":"Furniture built to last","title":"Nordwind","toneOfVoice":["Warm","Plain-spoken"],"values":["Craft","Honesty","Durability"]}},"design_system":{"additionalProperties":true,"type":"object","title":"Design System","description":"The brand's design system: its `tokens`, such as `colors` and `fonts`, and its component sections. It's stored as you send it. When you include `tokens`, send it as an object. It can be empty, such as while the brand is being generated.","example":{"tokens":{"colors":[{"label":"Primary","token":"primary","value":"#0B7A3B"},{"label":"Background","token":"background","value":"#FAF7F2"}],"fonts":[{"family":"Fraunces","role":"heading"},{"family":"Inter","role":"body"}]}}},"status":{"type":"string","enum":["generating","ready","failed"],"title":"Status","description":"`ready` once the brand is complete, `generating` while Base44 is still generating it, or `failed` if generation stopped. A brand that is still `generating` after 45 minutes without progress reads as `failed`.","example":"ready"},"generation_step":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generation Step","description":"Short progress label while `status` is `generating`, such as `foundation` or `components`. `null` otherwise.","example":"components"},"failure_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Failure Message","description":"Why generation failed, when it failed for a reason you can fix. `null` for any other brand, including one that failed for another reason.","example":"description is required"},"preview":{"$ref":"#/components/schemas/WorkspaceBrandPreview","description":"What generation has found so far. Use it to show the brand before `status` is `ready`. A brand you created yourself has an empty preview."}},"type":"object","required":["id","title","kit","design_system","status","generation_step","failure_message","preview"],"title":"WorkspaceBrand"},"WorkspaceBrandCard":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the brand.","example":"68e0c4a1b9d2f7003a5e1b42"},"title":{"type":"string","title":"Title","description":"Title of the brand, from `kit.title`. `Generating…` while the brand is being generated.","example":"Nordwind"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url","description":"URL of the brand's logo, from `kit.logo.url`. `null`, or an empty string, when the brand has none.","example":"https://media.base44.com/images/public/nordwind-logo.png"},"is_default":{"type":"boolean","title":"Is Default","description":"Whether this is the workspace's default brand.","example":true},"tagline":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tagline","description":"The brand's `kit.tagLine`, or its `kit.description` when it has no tagline. `null` when it has neither.","example":"Furniture built to last"},"colors":{"items":{"type":"string"},"type":"array","title":"Colors","description":"Up to 5 colors from the design system's color tokens, in token order.","example":["#0B7A3B","#FAF7F2"]},"fonts":{"items":{"type":"string"},"type":"array","title":"Fonts","description":"Up to 2 font families from the design system, the heading font first.","example":["Fraunces","Inter"]},"status":{"type":"string","enum":["generating","ready","failed"],"title":"Status","description":"`ready` once the brand is complete, `generating` while Base44 is still generating it, or `failed` if generation stopped. A brand that is still `generating` after 45 minutes without progress reads as `failed`.","example":"ready"},"failure_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Failure Message","description":"Why generation failed, when it failed for a reason you can fix. `null` for any other brand, including one that failed for another reason.","example":"description is required"}},"type":"object","required":["id","title","logo_url","is_default","tagline","colors","fonts","status","failure_message"],"title":"WorkspaceBrandCard"},"WorkspaceBrandList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/WorkspaceBrandCard"},"type":"array","title":"Items","description":"Every brand in the workspace.","example":[{"colors":["#0B7A3B","#FAF7F2"],"fonts":["Fraunces","Inter"],"id":"68e0c4a1b9d2f7003a5e1b42","is_default":true,"logo_url":"https://media.base44.com/images/public/nordwind-logo.png","status":"ready","tagline":"Furniture built to last","title":"Nordwind"}]}},"type":"object","required":["items"],"title":"WorkspaceBrandList"},"WorkspaceBrandPreview":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"The brand's name, or `null` until generation finds it.","example":"Nordwind"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url","description":"URL of a logo generation found, or `null` until it finds one.","example":"https://media.base44.com/images/public/nordwind-logo.png"},"colors":{"items":{"type":"string"},"type":"array","title":"Colors","description":"Colors found so far, primary color first.","example":["#0B7A3B","#FAF7F2"]},"fonts":{"items":{"type":"string"},"type":"array","title":"Fonts","description":"Font families found so far, most used first.","example":["Fraunces","Inter"]}},"type":"object","required":["title","logo_url","colors","fonts"],"title":"WorkspaceBrandPreview","description":"What a generation has found so far, before the design system is ready."},"WorkspaceBrandStarted":{"properties":{"status":{"type":"string","const":"success","title":"Status","description":"Always `success`.","example":"success"},"message":{"type":"string","title":"Message","description":"Short confirmation. Its wording can change, so don't parse it.","example":"Brand 'Nordwind' added"},"id":{"type":"string","title":"Id","description":"ID of the new brand.","example":"68e0c4a1b9d2f7003a5e1b42"}},"type":"object","required":["status","message","id"],"title":"WorkspaceBrandStarted"},"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."},"WorkspaceFont":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the font.","example":"68d4b2e1f0a9c8001b3e5f27"},"family_name":{"type":"string","title":"Family Name","description":"Family name, read from the font file. Use it as the CSS `font-family`.","example":"Acme Sans"},"display_family_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Family Name","description":"The broader family the font file says it belongs to, which groups stylistic variants such as an inline cut, or `null` when the file doesn't say.","example":"Acme"},"variant_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Variant Name","description":"Name of the stylistic variant, or `null` when the file doesn't name one.","example":"Inline"},"weight":{"type":"integer","title":"Weight","description":"Weight from 1 to 1000, usually a multiple of 100.","example":400},"style":{"type":"string","enum":["normal","italic"],"title":"Style","description":"Style of the font.","example":"normal"},"public_url":{"type":"string","title":"Public Url","description":"Public URL of the font as a WOFF2 file, for an `@font-face` `src`.","example":"https://media.base44.com/files/public/workspaces/67e0b12c4d8a3f005b21c9e4/fonts/3f9a1c2e_68d4b2e1f0a9c8001b3e5f27.woff2"},"created_by":{"type":"string","title":"Created By","description":"Email of the member who uploaded the font.","example":"dana@acme.com"},"created_date":{"type":"string","title":"Created Date","description":"When the font was uploaded, as a UTC timestamp in ISO 8601 format.","example":"2026-09-02T11:24:05.311000"}},"type":"object","required":["id","family_name","display_family_name","variant_name","weight","style","public_url","created_by","created_date"],"title":"WorkspaceFont","description":"One uploaded font: a single weight and style of a family."},"WorkspaceFontList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/WorkspaceFont"},"type":"array","title":"Items","description":"The workspace's fonts, newest first.","example":[{"created_by":"dana@acme.com","created_date":"2026-09-02T11:24:05.311000","display_family_name":"Acme","family_name":"Acme Sans","id":"68d4b2e1f0a9c8001b3e5f27","public_url":"https://media.base44.com/files/public/workspaces/67e0b12c4d8a3f005b21c9e4/fonts/3f9a1c2e_68d4b2e1f0a9c8001b3e5f27.woff2","style":"normal","weight":400}]},"total":{"type":"integer","title":"Total","description":"Number of fonts in `items`.","example":1}},"type":"object","required":["items","total"],"title":"WorkspaceFontList"},"WorkspaceGroupListResponse":{"properties":{"groups":{"items":{"$ref":"#/components/schemas/WorkspaceGroupSummary"},"type":"array","title":"Groups","description":"Every group in the workspace, oldest first.","example":[{"description":"Product designers","id":"68b4e1d2c9a7f3001e2d4b61","member_count":12,"name":"Design team","role":"editor","source":"custom","unresolved_count":0}]}},"type":"object","required":["groups"],"title":"WorkspaceGroupListResponse"},"WorkspaceGroupSummary":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the group.","example":"68b4e1d2c9a7f3001e2d4b61"},"name":{"type":"string","title":"Name","description":"Name of the group.","example":"Design team"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Short description of the group, or `null` when it has none.","example":"Product designers"},"source":{"type":"string","title":"Source","description":"`custom` for a group created in Base44, or `idp` for one your identity provider sends through SCIM. An `idp` group's name and members come from the identity provider.","example":"custom"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"Role every member gets from the group: `admin`, `editor`, `viewer`, or `no_access`. `null` when the group grants no role.","example":"editor"},"member_count":{"type":"integer","title":"Member Count","description":"Number of members.","example":12},"unresolved_count":{"type":"integer","title":"Unresolved Count","description":"Members your identity provider sent who don't have a Base44 account in the workspace yet. Always `0` for a `custom` group.","default":0,"example":0}},"type":"object","required":["id","name","source","member_count"],"title":"WorkspaceGroupSummary"},"WorkspaceMemberRow":{"properties":{"email":{"type":"string","title":"Email","description":"Email of the member or invitee, in the casing it was added with. Pass it as is to the endpoints that take a member's email.","example":"dana@acme.com"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name","description":"Full name, or `null` when there isn't one, as for an invitee without a Base44 account.","example":"Dana Levi"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id","description":"ID of the person's Base44 account, or `null` for an invitee without one.","example":"66f1c2a9b7e3d4001f8a2c55"},"role":{"type":"string","title":"Role","description":"Role in the workspace: `owner`, `admin`, `editor`, `viewer`, or `guest`.","example":"editor"},"status":{"type":"string","title":"Status","description":"`active` for a member, `pending` for someone invited who hasn't joined.","example":"active"},"assigned_via":{"type":"string","title":"Assigned Via","description":"`group` when a group grants the role, so it can only change at the group. `direct` otherwise.","default":"direct","example":"direct"},"access_state":{"type":"string","title":"Access State","description":"`blocked` when a group with no access blocks the member, so `role` grants nothing. `granted` otherwise.","default":"granted","example":"granted"},"invitation_expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invitation Expires At","description":"When a pending invitation expires, in ISO 8601 UTC without an offset. `null` for members who joined.","example":"2026-12-04T09:30:00"},"credits_used":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Credits Used","description":"Credits the member used in the workspace this month. `null` on other members' rows when you aren't an owner or admin.","example":128.5},"credit_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credit Limit","description":"The member's monthly credit limit, their own or the workspace default. `null` when neither is set.","example":500},"is_default_limit":{"type":"boolean","title":"Is Default Limit","description":"`true` when `credit_limit` is the workspace default.","default":false,"example":false},"apps_owned":{"type":"integer","title":"Apps Owned","description":"Apps the member owns in the workspace.","default":0,"example":4},"agents_owned":{"type":"integer","title":"Agents Owned","description":"Agents the member owns in the workspace.","default":0,"example":1},"apps_shared":{"type":"integer","title":"Apps Shared","description":"Apps a guest was invited to collaborate on. Always `0` for other roles.","default":0,"example":0},"agents_shared":{"type":"integer","title":"Agents Shared","description":"Agents a guest was invited to collaborate on. Always `0` for other roles.","default":0,"example":0},"last_active":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Active","description":"When the member last used credits in the workspace, in ISO 8601 UTC without an offset, or `null` if never.","example":"2026-10-03T14:12:08.512000"},"last_seen":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Seen","description":"When the member last used Base44 in this workspace, in ISO 8601 UTC without an offset, or `null` if not recorded. `null` on other members' rows when you aren't an owner or admin.","example":"2026-10-05T08:41:22.004000"}},"type":"object","required":["email","full_name","user_id","role","status","assigned_via","access_state","invitation_expires_at","credits_used","credit_limit","is_default_limit","apps_owned","agents_owned","apps_shared","agents_shared","last_active","last_seen"],"title":"WorkspaceMemberRow","description":"A member or pending invitee of the workspace."},"WorkspaceSkill":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the skill.","example":"68d2a1f4c9e7b3001f2a5c88"},"name":{"type":"string","title":"Name","description":"Name of the skill.","example":"brand-voice"},"description":{"type":"string","title":"Description","description":"When the skill applies.","example":"Use when writing any user-facing copy for our apps."},"body":{"type":"string","title":"Body","description":"The instructions the AI builder follows once it loads the skill.","example":"Write in a warm, plain-spoken voice. Use sentence case for headings and keep buttons to two words."},"enabled":{"type":"boolean","title":"Enabled","description":"Whether the AI builder can use the skill.","example":true},"created_by":{"type":"string","title":"Created By","description":"Email of the member who created the skill.","example":"dana@acme.com"},"created_date":{"type":"string","title":"Created Date","description":"Time the skill was created, in UTC, as an ISO 8601 timestamp.","example":"2026-08-01T09:15:00.123000Z"},"updated_date":{"type":"string","title":"Updated Date","description":"Time the skill last changed, in UTC, as an ISO 8601 timestamp.","example":"2026-08-03T14:02:10.456000Z"}},"type":"object","required":["id","name","description","body","enabled","created_by","created_date","updated_date"],"title":"WorkspaceSkill"},"WorkspaceSkillList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/WorkspaceSkillSummary"},"type":"array","title":"Items","description":"Every skill in the workspace, newest first.","example":[{"created_by":"dana@acme.com","created_date":"2026-08-01T09:15:00.123000Z","description":"Use when writing any user-facing copy for our apps.","enabled":true,"id":"68d2a1f4c9e7b3001f2a5c88","name":"brand-voice","updated_date":"2026-08-03T14:02:10.456000Z"}]},"total":{"type":"integer","title":"Total","description":"Number of skills in `items`.","example":1}},"type":"object","required":["items","total"],"title":"WorkspaceSkillList"},"WorkspaceSkillSummary":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the skill.","example":"68d2a1f4c9e7b3001f2a5c88"},"name":{"type":"string","title":"Name","description":"Name of the skill.","example":"brand-voice"},"description":{"type":"string","title":"Description","description":"When the skill applies.","example":"Use when writing any user-facing copy for our apps."},"enabled":{"type":"boolean","title":"Enabled","description":"Whether the AI builder can use the skill.","example":true},"created_by":{"type":"string","title":"Created By","description":"Email of the member who created the skill.","example":"dana@acme.com"},"created_date":{"type":"string","title":"Created Date","description":"Time the skill was created, in UTC, as an ISO 8601 timestamp.","example":"2026-08-01T09:15:00.123000Z"},"updated_date":{"type":"string","title":"Updated Date","description":"Time the skill last changed, in UTC, as an ISO 8601 timestamp.","example":"2026-08-03T14:02:10.456000Z"}},"type":"object","required":["id","name","description","enabled","created_by","created_date","updated_date"],"title":"WorkspaceSkillSummary"},"WriteFileResult":{"properties":{"path":{"type":"string","title":"Path","description":"The normalized path that was written.","example":"src/pages/About.jsx"},"bytes_written":{"type":"integer","title":"Bytes Written","description":"Size of the content written, in bytes.","example":214},"created":{"type":"boolean","title":"Created","description":"`true` when the file did not exist before this call.","example":true},"overwritten":{"type":"boolean","title":"Overwritten","description":"`true` when the file existed and was replaced. Never `true` unless you sent `overwrite`.","example":false},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings","description":"Advisories about the write that did not stop it, such as writing somewhere the app editor treats specially. Empty when there are none.","example":[]}},"type":"object","required":["path","bytes_written","created","overwritten","warnings"],"title":"WriteFileResult","description":"What the write did."},"_AccessModesView":{"properties":{"read_only":{"items":{"type":"string"},"type":"array","title":"Read Only","description":"Scopes to ask for when the app only needs to read.","example":["https://www.googleapis.com/auth/calendar.readonly"]},"full_access":{"items":{"type":"string"},"type":"array","title":"Full Access","description":"Scopes to ask for when the app also needs to make changes.","example":["https://www.googleapis.com/auth/calendar.readonly","https://www.googleapis.com/auth/calendar.events"]}},"type":"object","required":["read_only","full_access"],"title":"_AccessModesView","description":"Read-only / full-access scope groupings exposed via the catalog API."},"_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."},"backend__app__user_apps__app_group_shares__api__OperationResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Always `true`. A removal that doesn't happen returns an error instead.","default":true,"example":true}},"type":"object","required":["success"],"title":"OperationResponse"}},"securitySchemes":{"PersonalAccessTokenAuth":{"type":"http","scheme":"bearer","description":"Personal access token, sent as `Authorization: Bearer <token>`."},"WorkspaceApiKeyAuth":{"type":"apiKey","in":"header","name":"api_key","description":"Workspace API key, sent as `api_key: <key>`."}}},"security":[{"PersonalAccessTokenAuth":[]}],"servers":[{"url":"https://app.base44.com"}]}