# Fetch records Source: https://docs.qobra.co/api_reference/v1/endpoints/data_tables/fetch_records_data get /v1/tables/{data_table_key}/records Retrieves table records data ## Pagination This endpoint is paginated using parameters offset and limit The “next” key in the response allow you to easily follow pagination to get the next batch of records ## Variable api keys This endpoint is based on dynamic fields. This mean that all annotations that look like ``, will have to or will be replaced by their actual API keys. # Upsert records bulk Source: https://docs.qobra.co/api_reference/v1/endpoints/data_tables/ingest_records post /v1/tables/{data_table_key}/records/bulk Imports records in an existing table via an import. Create new records or update existing ones. # How to use it ## Create your data table To push data to a data table, you will need to setup first the table inside Qobra [here](https://app.qobra.co/objects/fields). You must select the Public API integration in the first step of creating a table. After creating the table you will need to set up all the fields you will use later. Qobra data table creation If you try editing your data table, you’ll find: * the `table-api-name`, that you can edit, to identify your data table * the `id-field-api-name`, that you can edit if needed * the `name-field-api-name`, that you can edit if needed Qobra edit data table On this page, you will also find each of your data table fields api keys (`field-api-key`). You can edit them if needed. ## Variable api keys This endpoint is based on dynamic fields. This mean that all annotations that look like ``, will have to or will be replaced by their actual API keys. ## Best practices for large imports We recommend splitting large imports into smaller batches (for example 1,000 to 5,000 records per request). Sending very large payloads in a single request can lead to network timeouts or failed imports. Sending very small payloads (for example, one or a few records) per request is discouraged, as it may quickly hit the API rate limit (80 requests per second across all endpoints) and significantly slow down ingestion. For full resynchronizations, you can combine batching with the all and debounce query parameters. ## Trouble shooting When trouble shooting a webhook import, first check you imports are present in Qobra’s import page [here](https://app.qobra.co/objects/imports). Qobra import history * if your imports do not appear in the list of previous imports (after refreshing the page), check the authentication * if your import is in warning, you’ll see the records which did not import, with the error’s detail Qobra import history error details * if your import seems successful, but data in your data table is not updated, your fields api name must mismatch the ones parameterized in Qobra. # Fetch import Source: https://docs.qobra.co/api_reference/v1/endpoints/imports/fetch_import get /v1/imports/{import_id} Retrieve the status and associated details of an import # How to use it After pushing data to Qobra with the [Record ingestion route](/api_reference/v1/endpoints/data_tables/ingest_records) or with one of the bulk upsert routes ([users](/api_reference/v1/endpoints/users/upsert_bulk_user), [managers](/api_reference/v1/endpoints/users/upsert_bulk_user_manager), [groups](/api_reference/v1/endpoints/users/upsert_bulk_user_group)), you’ll receive an `import_id`. Since Qobra ingestion is performed as a background task, you’ll have to fetch your import details to verify it went successfully. # Fetch record reporting data Source: https://docs.qobra.co/api_reference/v1/endpoints/reporting/fetch_record_reporting_data get /v1/reporting/{data_table_id}/records Retrieves record reporting data. A record report holds your compensation's data, consolidated around metrics on a record level. This endpoint is deprecated. Use the v2 endpoint instead. See [v2 documentation](/api_reference/v2/endpoints/data/fetch_records) ## Pagination This endpoint is paginated using parameters offset and limit The “next” key in the response allow you to easily follow pagination to get the next batch of records ## Variable api keys This endpoint is based on dynamic fields. This mean that all annotations that look like ``, will have to or will be replaced by their actual API keys. # Fetch statement reporting data Source: https://docs.qobra.co/api_reference/v1/endpoints/reporting/fetch_statement_reporting_data get /v1/reporting Retrieves statement reporting data. A statement report holds your compensation's data, consolidated around metrics on a statement level. This endpoint is deprecated. Use the v2 endpoint instead. See [v2 documentation](/api_reference/v2/endpoints/data/fetch_statements) ## Pagination This endpoint is paginated using parameters offset and limit The “next” key in the response allow you to easily follow pagination to get the next batch of records ## Variable api keys This endpoint is based on dynamic fields. This mean that all annotations that look like ``, will have to or will be replaced by their actual API keys. # Archive users bulk Source: https://docs.qobra.co/api_reference/v1/endpoints/users/archive_users delete /v1/users/archive/bulk Archive users in bulk. ## Behavior * **Transaction**: If one part of the payload is not parsable (or understandable by the system), we raise you an error and we don’t archive anyone from the payload. You have to fix your payload and send it to us again. And if the answer is a success, it means that every user to archive you sent us have been archived successfully * **Conflict Rule and archive**: If you try to archive a user that is managing someone on the given archive period, we raise you an error. * **Limit**: you can’t archive more than 50 users at a time. * **Error behavior**: In case of any error, your user wont be archived # Fetch users Source: https://docs.qobra.co/api_reference/v1/endpoints/users/fetch_users get /v1/users Retrieves users ## Response fields Each user in the response contains a fixed set of standard fields plus any custom user attributes configured for your Qobra account. **Standard fields (always returned):** * `id` — user ID (`ObjectId`) * `email` — user email * `name` — user name * `status` — one of `active`, `archived`, `inactive`, `pending` * `role` — user role * `currency` — user currency code * `calculation_currency` — currency used for commission calculations **Custom user attributes (returned when configured):** Every attribute defined under `Qobra settings > User attributes` appears in each user object under its own API key. Amount-type attributes also add a `_currency` field alongside the value. Fields such as `business_unit`, `team`, `region`, or `manager` are **not** standard fields on this endpoint. If they exist in your account, they are custom user attributes and appear under the API key you assigned to them in `Qobra settings > User attributes`. ### Discover your custom user attribute API keys Custom user attributes are defined per Qobra account, so their API keys are specific to your organization. 1. Open `Qobra settings > User attributes` in the Qobra app. 2. Locate the attribute you want (for example, a business unit attribute). 3. Copy its API key. This is the exact key that appears in the `GET /v1/users` response for every user that has a value for that attribute. Custom user attributes definition ## Pagination This endpoint is paginated using parameters offset and limit The “next” key in the response allow you to easily follow pagination to get the next batch of records ## Variable api keys This endpoint is based on dynamic fields. This mean that all annotations that look like ``, will have to or will be replaced by their actual API keys. # Upsert users bulk Source: https://docs.qobra.co/api_reference/v1/endpoints/users/upsert_bulk_user put /v1/users/bulk Upsert users in bulk. Create new users or update existing ones. ## How to use user attributes If you want to add custom columns to your user table, create them in `Qobra settings > User attributes` first. Custom user attributes definition Those attributes will have an auto-generated API key that you can overwrite. This key will allow you to provide value for those attribute at user creation through API. ## Variable api keys This endpoint is based on dynamic fields. This mean that all annotations that look like ``, will have to or will be replaced by their actual API keys. ## Asynchronous upsert This endpoint does not apply your payload during the request: it stores it and creates an **import** that applies it in the background. The response gives you the `import_id` of that import, and the number of items your request contained. ```json theme={null} { "import_id": "QfejsQZ6mpOVHT5Tt1SdNLPZ", "objects_imported": 2 } ``` A `200` therefore means *the import was created*, not *the payload was applied*. To know how it went, fetch the import with the [Fetch import route](/api_reference/v1/endpoints/imports/fetch_import) until its `status` is `finished`, then read its `error` flag: * `error: false` — every item of your payload has been upserted. * `error: true` — read `title` in `data_tables_details[].error` to know what happened. `ValidationError` means the payload was refused and **nothing** was upserted, `description` listing the invalid items. `PartialFailure` means part of it was applied: `description` counts the items that reported an issue, and `parsing_error_details` names the skipped ones — the most recent 100 — each with its `line_number`, `row` and `details`. Imports of a company run one after the other, so an import can stay `scheduled` for a while when another one is already running. A `400` is still returned during the request itself, for a malformed body or a payload above the limit — in that case no import is created. ## Merging successive calls with `debounce` A payload above the limit of 200 has to be split across several calls, and each call would otherwise create its own import — all queued behind one another. Pass `debounce`, a number of seconds, to hold the import open: every call to **the same endpoint** made during that window joins the same import instead of queuing a new one, and they all return the same `import_id`. The window is opened by the first call, and a later call joins it without pushing its start further. Each payload keeps its own validation. A refused payload upserts nothing, but the payloads merged beside it are still applied — the import then reports the reason of every refusal in `data_tables_details[].error`. Only calls to the same endpoint are merged: users, manager attributions and group attributions always get their own import. This is deliberate, since attributions need the users they reference to exist already — send your users first, wait for their import to finish, then send the attributions. ## Behavior * **Conflict Rule and upsert**: Be careful, email field on user is unique. For each user, if it already exist (according to email), we will update the existing one and if it does not exist yet, we will create it. * **Transaction**: If there is at least one user that triggers an error at import, none of the users of your call are upserted and the import finishes in error. You have to fix your payload and send it to us again. And if the import finishes without error, it means that every users you sent us have been upserted. This all-or-nothing applies to the payload of your own call: when several calls share an import through `debounce`, a refused payload does not prevent the others from being applied * **Repeated users**: sending the same user twice in one payload does not refuse the call. The first occurrence is upserted, and each later one is skipped and reported in `parsing_error_details` with a `details` such as `"email: duplicated (jane@qobra.co)"`. Two users are the same when they share the identifier we match on, compared case-insensitively for an email * **Empty Field Rule**: If you don't specify a field in upsert, this field won’t be modified. If you want to empty a field, you have to send us a value null associated to the given field api key * **Limit**: you can’t upsert more than 200 users at a time. * **Error behavior**: a refused payload upserts none of its users, but a user skipped on its own — a repeated user, or one we could not write — leaves all the others upserted. Read the import rather than assuming an all-or-nothing outcome # Upsert group attributions bulk Source: https://docs.qobra.co/api_reference/v1/endpoints/users/upsert_bulk_user_group put /v1/users/groups/bulk Upsert group attributions in bulk. ## Asynchronous upsert This endpoint does not apply your payload during the request: it stores it and creates an **import** that applies it in the background. The response gives you the `import_id` of that import, and the number of items your request contained. ```json theme={null} { "import_id": "QfejsQZ6mpOVHT5Tt1SdNLPZ", "objects_imported": 2 } ``` A `200` therefore means *the import was created*, not *the payload was applied*. To know how it went, fetch the import with the [Fetch import route](/api_reference/v1/endpoints/imports/fetch_import) until its `status` is `finished`, then read its `error` flag: * `error: false` — every item of your payload has been upserted. * `error: true` — read `title` in `data_tables_details[].error` to know what happened. `ValidationError` means the payload was refused and **nothing** was upserted, `description` listing the invalid items. `PartialFailure` means part of it was applied: `description` counts the items that reported an issue, and `parsing_error_details` names the skipped ones — the most recent 100 — each with its `line_number`, `row` and `details`. Imports of a company run one after the other, so an import can stay `scheduled` for a while when another one is already running. A `400` is still returned during the request itself, for a malformed body or a payload above the limit — in that case no import is created. ## Merging successive calls with `debounce` A payload above the limit of 200 has to be split across several calls, and each call would otherwise create its own import — all queued behind one another. Pass `debounce`, a number of seconds, to hold the import open: every call to **the same endpoint** made during that window joins the same import instead of queuing a new one, and they all return the same `import_id`. The window is opened by the first call, and a later call joins it without pushing its start further. Each payload keeps its own validation. A refused payload upserts nothing, but the payloads merged beside it are still applied — the import then reports the reason of every refusal in `data_tables_details[].error`. Only calls to the same endpoint are merged: users, manager attributions and group attributions always get their own import. This is deliberate, since attributions need the users they reference to exist already — send your users first, wait for their import to finish, then send the attributions. ## Behavior * **Transaction**: If one group attribution is not parsable, none of the attributions of your call are upserted and the import finishes in error. You have to fix your payload and send it to us again. And if the import finishes without error, it means that every group attribution you sent us have been upserted. This all-or-nothing applies to the payload of your own call: when several calls share an import through `debounce`, a refused payload does not prevent the others from being applied * **Conflict Rule and upsert**: Be careful, each user can only have one group by dimension for a given period. Given new attributions dates will override conflicting dates already present in Qobra. Also, if you send a payload with two groups attributions for the same user over the same period the import is refused. * **Limit**: you can’t update more than 200 group attributions at a time. * **Error behavior**: In case of any error, your group attributions wont be upserted # Upsert manager attributions bulk Source: https://docs.qobra.co/api_reference/v1/endpoints/users/upsert_bulk_user_manager put /v1/users/managers/bulk Upsert managers relationships in bulk. ## Asynchronous upsert This endpoint does not apply your payload during the request: it stores it and creates an **import** that applies it in the background. The response gives you the `import_id` of that import, and the number of items your request contained. ```json theme={null} { "import_id": "QfejsQZ6mpOVHT5Tt1SdNLPZ", "objects_imported": 2 } ``` A `200` therefore means *the import was created*, not *the payload was applied*. To know how it went, fetch the import with the [Fetch import route](/api_reference/v1/endpoints/imports/fetch_import) until its `status` is `finished`, then read its `error` flag: * `error: false` — every item of your payload has been upserted. * `error: true` — read `title` in `data_tables_details[].error` to know what happened. `ValidationError` means the payload was refused and **nothing** was upserted, `description` listing the invalid items. `PartialFailure` means part of it was applied: `description` counts the items that reported an issue, and `parsing_error_details` names the skipped ones — the most recent 100 — each with its `line_number`, `row` and `details`. Imports of a company run one after the other, so an import can stay `scheduled` for a while when another one is already running. A `400` is still returned during the request itself, for a malformed body or a payload above the limit — in that case no import is created. ## Merging successive calls with `debounce` A payload above the limit of 200 has to be split across several calls, and each call would otherwise create its own import — all queued behind one another. Pass `debounce`, a number of seconds, to hold the import open: every call to **the same endpoint** made during that window joins the same import instead of queuing a new one, and they all return the same `import_id`. The window is opened by the first call, and a later call joins it without pushing its start further. Each payload keeps its own validation. A refused payload upserts nothing, but the payloads merged beside it are still applied — the import then reports the reason of every refusal in `data_tables_details[].error`. Only calls to the same endpoint are merged: users, manager attributions and group attributions always get their own import. This is deliberate, since attributions need the users they reference to exist already — send your users first, wait for their import to finish, then send the attributions. ## Behavior * **Transaction**: If there is at least one manager relationship that trigger an error at import, none of the relationships of your call are upserted and the import finishes in error. You have to fix your payload and send it to us again. And if the import finishes without error, it means that every managers relationships you sent us have been upserted. This all-or-nothing applies to the payload of your own call: when several calls share an import through `debounce`, a refused payload does not prevent the others from being applied. * **Conflict Rule and upsert**: Be careful, each user can only have one manager for a given period. Given new management dates will override conflicting dates already present in Qobra. Also, if you send a payload with two managers relationship for the same user the import is refused. * **Limit**: you can’t update more than 200 management relationship at a time. * **Error behavior**: In case of any error, your manager attributions wont be upserted # Introduction Source: https://docs.qobra.co/api_reference/v1/introduction Using the API endpoints ## Versions The Qobra API is versioned. Breaking changes will occur when we change the version, and you'll be notified by email two months in advance. You are currently viewing the **v1** API documentation. This version is fully functional. Its reporting endpoints are deprecated in favour of v2; every other v1 endpoint is maintained. ### Migrate to v2 (recommended for reporting) **A v2 is available**, and it covers **reporting only** — it is not a new version of the whole API. It brings significant improvements to reporting: * **Data Structures**: Tables containing your commission data (statements reporting and records reporting) * **Schema Discovery**: Dynamically inspect available fields and their types * **ID-Based Pagination**: Efficient navigation through large datasets using record IDs **Endpoints deprecated on v1, and their v2 replacement:** * `GET /v1/reporting` → see [v2 documentation](/api_reference/v2/endpoints/data/fetch_statements) * `GET /v1/reporting/{data_table_id}/records` → see [v2 documentation](/api_reference/v2/endpoints/data/fetch_records) **New endpoints in v2:** * `GET /data-structures` → [List data structures](/api_reference/v2/endpoints/discovery/list_data_structures) * `GET /data-structures/{table_id}/fields` → [Get fields schema](/api_reference/v2/endpoints/discovery/get_fields_schema) 👉 **We strongly recommend migrating to v2 for all reporting use cases.** Check out the [v1 → v2 migration guide](/api_reference/v2/migration_guide) to facilitate your transition. All other v1 endpoints — users, group and manager attributions, data tables and imports — stay on v1 and are maintained. They have no v2 equivalent and nothing to migrate. The deprecated reporting endpoints are still served and no removal date is announced: as with any breaking change, you would be notified by email two months in advance. ## Authentication ### Generate your API key To authenticate your requests, you'll need `API keys`, managed in Qobra's global settings under the [**API** section](https://app.qobra.co/parameters/api). Qobra API keys *API Keys are strictly personal and based on the user account that generated the keys.* Once you have retrieved those keys, you can authenticate to the API using request header ### Request Header To identify yourself, just put a header containing the API key as specified below. ```json theme={null} { "X-API-Key": } ``` ## Base URL Qobra's base api url is `https://api.qobra.co//` For the first version of the api, you'll have to call `https://api.qobra.co/v1/` ## MCP server (AI assistants) Qobra also provides an **MCP server** built on the Model Context Protocol (MCP). It exposes read-only tools so an MCP client like Claude, ChatGPT, or Dust can query your Qobra data (data tables, users, quotas, plans, reports, statements, and more) directly from a conversation. It is separate from the REST API and uses OAuth instead of an API key. Available tools, prerequisites, and how to connect Claude, ChatGPT, or Dust. # Migration guide v1 → v2 Source: https://docs.qobra.co/api_reference/v2/migration_guide What moves to v2, what stays on v1, and how to migrate your reporting integration ## Do you need to migrate? Only the **reporting** endpoints have a v2. v2 is not a new version of the whole API — it is a reporting-focused subset. If your integration does not call `GET /v1/reporting` or `GET /v1/reporting/{data_table_id}/records`, you have nothing to migrate. ## Support status of every endpoint | Endpoint | Status | Action | | - | - | - | | `GET /v1/reporting` | Deprecated | Migrate to [fetch statements records](/api_reference/v2/endpoints/data/fetch_statements) | | `GET /v1/reporting/{data_table_id}/records` | Deprecated | Migrate to [fetch reporting records](/api_reference/v2/endpoints/data/fetch_records) | | `GET /v1/users` | Maintained on v1 | Nothing to do | | `PUT /v1/users/bulk` | Maintained on v1 | Nothing to do | | `DELETE /v1/users/archive/bulk` | Maintained on v1 | Nothing to do | | `PUT /v1/users/groups/bulk` | Maintained on v1 | Nothing to do | | `PUT /v1/users/managers/bulk` | Maintained on v1 | Nothing to do | | `GET /v1/tables/{data_table_key}/records` | Maintained on v1 | Nothing to do | | `POST /v1/tables/{data_table_key}/records/bulk` | Maintained on v1 | Nothing to do | | `GET /v1/imports/{import_id}` | Maintained on v1 | Nothing to do | The deprecated reporting endpoints are still served today and no removal date is announced. As with any breaking change on the Qobra API, you would be notified by email two months in advance. The endpoints maintained on v1 have no end-of-life date — there is no v2 equivalent to move to. ## What changes on reporting Statements now take a `table_id`, discovered through `/v2/data-structures` `/v2/data-structures/{table_id}/fields` returns the exact keys, types and formats of your data `offset` is replaced by `start_id`, and the response hands you a ready-made `next_url` `last_modified_after` / `last_modified_before` let you pull only what moved ### Endpoint mapping | v1 | v2 | | - | - | | `GET /v1/reporting` | `GET /v2/reporting/{table_id}/statements` | | `GET /v1/reporting/{data_table_id}/records` | `GET /v2/reporting/{table_id}/records` | The v1 statements endpoint took no table in its path. In v2 both endpoints do, so start by listing your data structures to get the `table_id` to call. ### Parameter mapping | v1 | v2 | Notes | | - | - | - | | `offset` | `start_id` | Pass the ID of the last record received, or follow `meta.next_url` | | `limit` | `limit` | Unchanged in meaning, accepts 1 to 2000 | | — | `last_modified_after` | New. ISO 8601 datetime, for incremental syncs | | — | `last_modified_before` | New. ISO 8601 datetime | ### Response shape The envelope changes and the record keys are prefixed. ```json v1 theme={null} { "count": 4570, "next": "https://api.qobra.co/v1/reporting?offset=100&limit=100", "data": [ { "id": "6545110915808dbcc34531ef", "date": "2023-11-30", "payment": 8500.0, "payment_currency": "USD", "user": { "id": "5537a5b0...", "email": "john-doe@company.com" } } ] } ``` ```json v2 theme={null} { "data": [ { "standard.id": "65a1b2c3d4e5f6g7h8i9j0k1", "standard.date": "2024-01-01", "standard.total_commission": { "value": 8500.0, "currency": "USD" }, "standard.user": { "id": "507f191e...", "email": "sarah@company.com" } } ], "meta": { "next_start_id": "65a1b2c3d4e5f6g7h8i9j999", "has_more": true, "next_url": "https://api.qobra.co/v2/reporting/507f.../statements?start_id=65a1b2c3d4e5f6g7h8i9j999&limit=1000" } } ``` Three differences to handle in your parser: * **Envelope**: `count` / `next` / `data` becomes `data` / `meta`. Page forward with `meta.next_url` and stop on `meta.has_more`. * **Prefixed keys**: every field is namespaced by its origin — `standard.` for built-in fields, `custom.` for your custom metrics, `datatable.` for data table fields. * **Currency amounts**: the v1 pair `` + `_currency` becomes a single object `{ "value": …, "currency": … }`. Field keys are specific to your account. Do not guess them from this page — read them from [the fields endpoint](/api_reference/v2/endpoints/discovery/get_fields_schema), which returns every `api_key` with its type, format, currencies and enum values. ## Migrate in three steps Call `GET /v2/data-structures`. Each entry carries its `id`, its `type` (`statement_reporting` or `record_reporting`) and `links` pointing at the endpoints to call next. Call `GET /v2/data-structures/{table_id}/fields` and match each v1 key you consume to its v2 `api_key`. Store the returned `schema_hash` to detect later schema changes. Replace the `offset` loop with `meta.next_url`, and unwrap currency objects where your v1 code read a `_currency` sibling key. The [quickstart](/api_reference/v2/quickstart) runs these three calls end to end, with cURL, Python and JavaScript snippets you can copy. Your API key is unchanged — the same `X-API-Key` header authenticates both versions, and v1 and v2 can run side by side while you test. ## Checklist Call `/v2/data-structures` once to resolve the statement reporting `table_id`, then paginate with `meta.next_url` instead of incrementing `offset`. Those columns disappear. Read `value` and `currency` from the field's object instead, or flatten it back to two columns in your transformation layer. Nothing to migrate. Those endpoints stay on v1 and are maintained. Compare the `schema_hash` returned by the fields endpoint between two runs. This has no v1 equivalent. ## Next steps Run the three v2 calls end to end in 5 minutes Every v2 parameter and response field Need a hand? Reach us at [support@qobra.co](mailto:support@qobra.co) or through the in-app chat. # SSO Source: https://docs.qobra.co/authentication_documentation/sso/sso_documentation ## Introduction Single sign-on (SSO) is an authentication method that enables users to securely authenticate with multiple applications and websites by using just one set of credentials. Qobra’s SSO is implemented based on SAML (open standard used for authentication). This feature is pen-tested yearly by independent contractors. There are 3 parties involved in a SSO connection on Qobra: * The user who wishes to connect with his email address * Qobra, the application where the user wants to authenticate * The identity provider (IDP) which authenticates users and grand them access to the platform, such as `Okta`, `AzureDirectory`, `Keycloak`, etc … ## Setup You can manually setup your SSO in the Qobra platform (company settings - security). SSO setup dialog ### Step 1: Retrieve data from Qobra You’ll have first to retrieve the following information fields below, and input it in your IDP: * `Entity ID`: unique identifier that is used to identify a specific entity (here your Qobra account) in the SAML authentication. * `Redirect URL` or `Callback URL`: The callback URL represents the place where people will be redirected to after authenticating with their identity provider. ### Step 2: Input data in Qobra Then, you’ll have to fill in both field below to complete SAML setup: * `Domain name`: domain name used in the company's user email addresses. This field will default to your email’s domain name. * `Metadata URL`: URL of the SAML metadata, an XML document which contains information necessary for interaction with Identity Provider(IDP). This document contains essentials data to make the connection secure and successful (ex: URLs of endpoints, information about supported bindings, identifiers and public keys). The metadata available at the metadata URL should look something like this: ```xml theme={null} MIID7T...T7yNJg== MIID7TCC......QsT7yNJg== urn:oasis:names:tc:SAML:2.0:nameid-format:transient Administrator name@emailprovider.com ``` ### Step 3: Multiple domain names (optional) If your company currently has more then one domain name (because of a merger or rebranding typically), you can [contact the support](mailto:support@qobra.co) for them to fill in a second domain name for your company. You can’t do this action on your own for security purposes. ## Identity provider tutorials You'll find here step by step tutorials for our customers top 3 identity providers. If you'd like another identity provider to be present here, just ask us [here](mailto:tanguy@qobra.co). 1. Go to your admin dashboard, in the Applications tab. 2. Click on the button **“Create a new app integration”** and select SAML 2.0 New app integration 3. Fill the basic info of the app (App name: Qobra, logo, etc.) Fill in basic info 4. Fill the SAML settings as shown below. There is no specificity in the advanced settings for this setup but your organization could have some specific constrains to be filled. Please contact your organization IT for more information. Fill in SAML settings 5. You can finish the setup by providing the following settings. Finish the setup 6. Congratulations you now have access to the metadata URL. Access the metadata URL 1. Go to the `Enterprise Applications` tab and click the `New Application` button. Enterprise Applications Tab 2. Select the `Microsoft Entra SAML Toolkit` application and put `Qobra` in the name field. Saml Toolkit Application Gallery You can now click on the `Create` button 3. Once on your new application, go to the `Single sign-on` tab and click on `SAML` Single sign-on tab 4. You can now click on the `Edit` button to start putting the SAML data Fill in SAML data 5. Go to your SSO settings at [https://app.qobra.co/parameters/security](https://app.qobra.co/parameters/security) Qobra SSO data 6. Thanks to the information gathered on your SSO settings you can now fill those 3 fields: * Entity ID * Reply URL (`Redirect URL` in Qobra) * Sign on URL (value: [https://app.qobra.com/auth](https://app.qobra.com/auth)) Fill SAML data After filling those fields, you can now click on the `Save` button. 5. Copy the `App Federation Metadata Url` in the `SAML Certificates` section and fill it in Qobra SSO config (`Metadata URL` field) App Federation Metadata Url You should also fill the domain name of your email in the `Domain name` field Update Qobra SAML config 1. Go to administration UI to add a client. Add a client 2. Go to the next step and complete login settings as shown below. Complete login settings 3. Once the app is created, you MUST update theses parameters on client settings: * Sign Assertion: ON Fill in 'Sign assertion' option * Name ID Format: Email Fill in 'Name ID Format' option * Signing key config: OFF Fill in 'Signing key config' option 4. Now you can access the metadata URL, as shown below Access the metadata URL ## Troubleshooting ### Can’t find the Metadata URL * If you use Okta, KeyCloak or AzureDirectory, refer to the documentation about those specific identity providers. * If your identity provider only allow clients to download your metadata, you’ll have to serve them through an URL (S3, cloud storage, static server, etc …). Qobra does not allow the upload of SAML metadata in the platform. ### Metadata URL is rejected The host of your Metadata URL may point to an internal destination. It happened when you self-host your identity provider on your internal network. As a security measure and because we cannot access this internal host, we block the request. You must serve your metadata through a public URL. ### Can’t authenticate after registering your SSO Check your Metadata URL by visiting it directly on your browser: * The page should not return an error * The page should return an XML file * The assertions should be signed in your XML file ### Issues with setting home realm discovery As a security measure, an admin is only able to set a home realm discovery matching the domain name of his email address. To change it to another client domain, you should ask the support for help. # Attio Source: https://docs.qobra.co/integration_documentation/crms/attio ## Introduction Qobra connects to Attio through an application, which needs to be installed via an Oauth check. The integration is used to synchronize your Attio data, both **Objects** (standard and custom) and **Lists**, with their fields and their content. This integration is readonly and does not write anything in your Attio workspace. ## Access ### Oauth authentication & scopes To connect your Attio to Qobra, you'll need to go through an Oauth flow, granting us access to several read-only scopes: * `record_permission:read` (Read the content of your records) * `object_configuration:read` (Read objects and their attributes) * `list_configuration:read` (Read lists and their attributes) * `list_entry:read` (Read list entries) * `user_management:read` (Read your workspace members) You'll find more information about Attio scopes [here](https://docs.attio.com/rest-api/guides/authentication). ## Synchronization ### Refresh Frequency Qobra keeps your Attio data up to date through regular synchronizations: * **Scheduled Full Refresh**: on a recurring schedule, we perform a complete sync of all your Attio data * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings ### Total synchronization This integrations does total synchronization, meaning the whole document will be synced at every refresh. Unlike CRMs such as Salesforce or Hubspot, Attio does not expose a record-level last modified date. As a result, Qobra performs a complete refresh of your data on every synchronization rather than an incremental one. ### Supported tables The integration relies on Attio's schema discovery, so it supports all the data available in your workspace: * **Objects**: all standard objects (such as `People`, `Companies`, `Deals`) as well as any custom object defined in your workspace, with their standard and custom attributes. * **Lists**: each list is synchronized as its own table, including its list attributes and its parent record. User attribution in Qobra is based on your Attio **workspace members**, not on the standard `Users` object (which represents the end-users of your own product). ### Supported types You’ll find below the fields types supported by the Qobra integration, and their mapping to Qobra types. | **Attio type** | **Qobra type** | | - | - | | `text` | `string` | | `personal-name` | `string` | | `email-address` | `string` | | `phone-number` | `string` | | `domain` | `string` | | `location` | `string` | | `number` | `number` | | `rating` | `number` | | `currency` | `amount` | | `checkbox` | `bool` | | `date` | `date` | | `timestamp` | `date` | | `select` | `picklist` or `multipicklist` | | `status` | `picklist` or `multipicklist` | | `record-reference` | `object` | | `actor-reference` | `user` | ### Multi-currency amounts Attio's built-in `currency` attribute only stores a single ISO currency code per workspace, which is not enough when your records mix currencies. To work around this, Qobra lets you map any `number` attribute to Qobra's `amount` type and pick a **separate attribute that carries the ISO currency code** for each record. Use this when you need to import amounts (revenue, ACV, commission base…) whose currency varies from one record to the next. #### Configure the currency code source 1. In Qobra, open the field mapping for a `number` attribute imported as an `amount`. 2. Under **Currency code**, either keep **Company currency** (the default, applied to every record) or pick an Attio attribute to read the code from for each record. 3. Save. The new mapping is applied on the **next import** — existing rows keep their previously imported currency until then. 4. Trigger a manual refresh from the integration settings if you want the change to take effect immediately. #### Supported source attributes The currency code can come from any of these Attio attribute types on the same table: | **Attio attribute type** | **How the code is read** | | - | - | | `text` | The raw text value. | | `select` (Picklist) | The **option label**, which must be the code. | | `status` (Picklist) | The **status label**, which must be the code. | Multi-select `select` and `status` attributes are **not** eligible — a multi-valued attribute has no single code to read. #### ISO code requirement and fallback Values (or option labels) must be a valid ISO 4217 code such as `EUR`, `USD`, or `GBP`. If the value is empty, unrecognized, or non-string, Qobra silently falls back to your **company currency**. Casing and surrounding whitespace are normalized, so `usd`, `USD` and `USD` are treated the same. For `select` and `status` sources, name the options directly with the ISO code (for example, an option labeled `EUR` rather than `Euro`), otherwise the record will fall back to the company currency. #### Attio Lists For a **List** table, the currency code attribute must live **on the List itself**, not on the parent Object. Only attributes exposed on the same List entry can be resolved during import. [Contact Qobra support](mailto:support@qobra.co) for more information. # Hubspot Source: https://docs.qobra.co/integration_documentation/crms/hubspot ## Introduction Qobra connects to Hubspot through an application, which needs to be installed via an Oauth check. The integration is used to synchronize tables from Hubspot, with their fields and their content. The application supports both standard and custom objects from the CRM. This integration is readonly and does not write anything in your Hubspot. ## Access ### Oauth authentication & scopes To connect your Hubspot to Qobra, you’ll need to go through an Oauth flow, granting us access to several scopes: * `crm.objects.companies.read` * `crm.schemas.companies.read` * `crm.objects.contacts.read` * `crm.schemas.contacts.read` * `crm.objects.deals.read` * `crm.schemas.deals.read` * `crm.objects.owners.read` * `crm.objects.custom.read` (optional) * `crm.schemas.custom.read` (optional) * `crm.objects.goals.read` (optional) * `e-commerce` (optional) * `crm.objects.leads.read` (optional) You’ll find more information about Hubspot application scopes [here](https://developers.hubspot.com/docs/api/scopes). ## Synchronization ### Refresh Frequency To ensure your Hubspot data is always up to date in Qobra, we maintain the following synchronization schedule: * **Daily Full Refresh**: Every night, we perform a complete sync of all your Hubspot data * **Bi-hourly Updates**: Every 2 hours throughout the day, we sync any recent changes * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings ### Incremental synchronization This integration is an incremental synchronization. This means that all imports are based on each record last modified date, and that we only refresh what has been modified in the system since the previous import. When an import is launched, we thus refresh all records that have been modified since the start of the last successful import. Once we have refresh range, synchronization work is split and parallelized in smaller tasks for performance enhancement. The synchronization tasks are split according to the last modified date. It is important to note that Qobra admin can manually trigger complete synchronization in the application. For Hubspot, we base this synchronization on the `lastModifiedDate` field, present in each table. ### Supported tables You’ll find here all tables supported by the Qobra integration. | **Tables** | **Name field** | **Currency field** | **Jointures** | | - | - | - | - | | `Deals` | `dealname` | `deal_currency_code` | `Companies`, `Contacts` | | `Companies` | `name` | | | | `Line Items` | `name` | `hs_line_item_currency_code` | `Deals` | | `Meetings` | `hs_meeting_title` | | `Companies`, `Deals` | | `Calls` | `hs_call_title` | | | | `Emails` | `hs_email_subject` | | | | `Tasks` | `hs_task_subject` | | | | `Contacts` | `email` | | `Companies`, `Meetings` | | `Leads` (optional) | `hs_lead_name` | | `Companies`, `Contacts` | | `Goals` (optional) | `hs_goal_name` | | | | `Custom tables` (optional) | `primaryDisplayProperty` | | | ### Supported types You’ll find below the fields types supported by the Qobra integration, and their mapping to Qobra types. | **Hubspot type** | **Qobra type** | | - | - | | `number` | `amount`, `number` | | `user` | `user` | | `string` | `string` | | `enumeration` | `picklist`, `user` | | `bool` | `bool` | | `datetime` | `date` | | `date` | `date` | ### Custom querying With this integration, you can apply a custom query to your table synchronization, to avoid synchronizing too much information, consequently making synchronization quicker. Custom queries are based on the hubspot API where conditions. For instance, you could add a custom query like below to filter your Deal table on stage field. ```json theme={null} { "propertyName": "dealstage", "operator": "IN", "values": ["appointmentscheduled", "contractsent", "qualifiedtobuy"] } ``` You'll find more information in Hubspot's documentation [here](https://developers.hubspot.com/docs/guides/api/crm/search#filter-search-results). [Contact Qobra support](mailto:support@qobra.co) for more information. # Microsoft Dynamics Source: https://docs.qobra.co/integration_documentation/crms/microsoft_dynamics ## Introduction Qobra connects to Microsoft Dynamics through an application, which needs to be installed via an Oauth check. The integration is used to synchronize tables from Microsoft Dynamics, with their fields and their content. This integration is readonly and does not write anything in your Microsoft Dynamics. ## Access ### Oauth authentication & scopes To connect your Microsoft Dynamics to Qobra, you’ll need: 1. To fill in the domain name of your Microsoft Dynamics instance. It should look something like this: `https://org123.crm12.dynamics.com`. 2. To go through an Oauth flow, granting us access to the `.default` scope. You’ll find more information about this scope [here](https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc#the-default-scope). ## Synchronization ### Refresh Frequency We keep your Microsoft Dynamics data synchronized with Qobra using the following schedule: * **Daily Full Refresh**: Every night, we perform a complete sync of all your Dynamics data * **Bi-hourly Updates**: Every 2 hours throughout the day, we sync any recent changes * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings ### Incremental synchronization This integration is an incremental synchronization. This means that all imports are based on each record last modified date, and that we only refresh what has been modified in the system since the previous import. When an import is launched, we thus refresh all records that have been modified since the start of the last successful import. Once we have refresh range, synchronization work is split and parallelized in smaller tasks for performance enhancement. The synchronization tasks are split according to the last modified date. It is important to note that Qobra admin can manually trigger complete synchronization in the application. ### Supported types You’ll find below the fields types supported by the Qobra integration, and their mapping to Qobra types. | **Microsoft Dynamics type** | **Qobra type** | | - | - | | `Decimal` | `number` | | `Integer` | `number` | | `Money` | `amount` | | `String` | `string` | | `Memo` | `string` | | `Picklist` | `picklist` | | `Multipicklist` | `multipicklist` | | `Boolean` | `bool` | | `DateTime` | `date` | # Odoo Source: https://docs.qobra.co/integration_documentation/crms/odoo ## Introduction Qobra connects to Odoo thanks to a user API key. The integration is used to synchronize tables, with their fields and their content. This integration is readonly and does not write anything in your Odoo account. ## Setup 1. Create your API key, as documented [here](https://www.odoo.com/documentation/18.0/developer/reference/external_api.html#api-keys). 2. Fill in information in Qobra: * `Username`: the email of the person who created the API key * `Password / API Key`: API key (it is not advised connecting directly with password) * `Instance URL`: Your Odoo instance URL * `Database`: Odoo Database Odoo setup in Qobra ## Synchronization ### Refresh Frequency For Odoo integrations, we maintain a different synchronization schedule than other CRMs: * **Bi-hourly Full Refresh**: Every 2 hours throughout the day, we perform a complete sync of your Odoo data * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings Note: Unlike other CRM integrations, Odoo uses full refreshes every 2 hours instead of combining daily full refreshes with incremental updates. ### Supported types You’ll find below the fields types supported by the Qobra integration, and their mapping to Qobra types. | **Odoo type** | **Qobra type** | | - | - | | `float` | `number`, `amount` | | `integer` | `number`, `user` | | `monetary` | `amount` | | `text` | `string` | | `char` | `string`, `user` | | `boolean` | `bool` | | `selection` | `picklist` | | `date` | `date` | | `datetime` | `date` | | `user` | `user` | # Pipedrive Source: https://docs.qobra.co/integration_documentation/crms/pipedrive ## Introduction Qobra connects to Pipedrive through an application, which needs to be installed via an Oauth check. The integration is used to synchronize tables from Pipedrive, with their fields and their content. This integration is readonly and does not write anything in your Pipedrive. ## Access ### Oauth authentication & scopes To connect your Pipedrive to Qobra, you’ll need to go through an Oauth flow, granting us access to several scopes: * `base` (Access to basic information) * `deals:read` (Deals: Read only) * `mail:read` (Mail: Read only) * `activities:read` (Activities: Read only) * `contacts:read` (Contacts: Read only) * `products:read` (Products: Read only) * `users:read` (Read users data) * `recents:read` (See recent account activity) * `search:read` (Search for all data) * `leads:read` (Leads: Read only) * `goals:read` (Goals: Read only) You’ll find more information about Pipedrive application scopes [here](https://pipedrive.readme.io/docs/marketplace-scopes-and-permissions-explanations). ## Synchronization ### Refresh Frequency Your Pipedrive data is kept in sync with Qobra through our automated refresh schedule: * **Daily Full Refresh**: Every night, we perform a complete sync of all your Pipedrive data * **Bi-hourly Updates**: Every 2 hours throughout the day, we sync any recent changes * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings ### Incremental synchronization This integration is an incremental synchronization. This means that all imports are based on each record last modified date, and that we only refresh what has been modified in the system since the previous import. When an import is launched, we thus refresh all records that have been modified since the start of the last successful import. Once we have refresh range, synchronization work is split and parallelized in smaller tasks for performance enhancement. The synchronization tasks are split according to the last modified date. It is important to note that Qobra admin can manually trigger complete synchronization in the application. ### Supported tables You’ll find here all tables supported by the Pipedrive integration: | **Pipedrive table** | **Name field** | | - | - | | `Deals` | `title` | | `Activities` | `subject` | | `Organizations` | `name` | | `Persons` | `name` | | `Products` | `name` | ### Supported types You’ll find below the fields types supported by the Qobra integration, and their mapping to Qobra types. | **Pipedrive type** | **Qobra type** | | - | - | | `double` | `number` | | `int` | `number` | | `monetary` | `amount` | | `varchar` | `string` | | `enum` | `picklist` | | `status` | `picklist` | | `set` | `multi-picklist` | | `date` | `date` | | `user` | `user` | # Salesforce Source: https://docs.qobra.co/integration_documentation/crms/salesforce ## Introduction Qobra connects to Salesforce through a connected app, which needs to be installed via an Oauth check. The integration is used to synchronize all objects, their fields and their content. The integration supports standard or custom objects, and standard or custom fields in those objects. This integration is readonly and does not write anything in your Salesforce. ## Access ### Oauth authentication & scopes To connect your Salesforce to Qobra, you’ll need to go through an Oauth flow, granting us access to two Salesforce scopes: * `Perform requests at any time` (refresh\_token, offline\_access) * `Manage user data via APIs` (api) You’ll find more information about Salesforce connected app scopes [here](https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_tokens_scopes.htm\&type=5). ### OAuth login – common pitfalls When logging into Salesforce, please pay attention to the following points: * Make sure you are logging into the correct Salesforce environment (Production or Sandbox). * If you are logging in as an API-only user, ensure this user is fully logged out of Salesforce before starting the OAuth connection. * Be careful when using Salesforce custom domains and verify you are authenticating through the correct domain. If you encounter the following error during the OAuth flow `OAUTH_APPROVAL_ERROR_GENERIC`, and the callback URL contains `error_description=app+must+be+installed+into+org`, follow these steps: * Go to *Setup* → *Connected Apps OAuth Usage* * Find the Qobra connected app * If the status shows *Install* (or *Approve*), click it * Retry the OAuth connection ### Access restriction The Qobra integrations has access to all the data the user who connected has access to. A common practice for our customers is to create an `API only` user in Salesforce to give Qobra’s access through this user (unlimited, enterprise, and performance Salesforce edition orgs get five free `API only` licenses). You can restrict this user’s access to Salesforce data, and then give us access through the Oauth flow to restrict Qobra’s access to a part of your Salesforce. Follow [this tutorial](https://admin.salesforce.com/blog/2023/best-practices-for-configuring-your-integration-user) to implement Salesforce's best security practices. ## Synchronization ### Refresh Frequency Qobra keeps your Salesforce data up to date through regular synchronizations: * **Daily Full Refresh**: Every night, we perform a complete sync of all your Salesforce data * **Bi-hourly Updates**: Every 2 hours throughout the day, we sync any recent changes * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings For Salesforce, we base this synchronization on the `LastModifiedDate` field, present in each table. | **Salesforce type** | **Qobra type** | | - | - | | `int` | `number` | | `double` | `number` | | `percent` | `percentage` | | `currency` | `amount` | | `picklist` | `picklist` | | `multipicklist` | `multipicklist` | | `textarea` | `string` | | `string` | `string` | | `combobox` | `string` | | `phone` | `string` | | `email` | `string`, `user` | | `boolean` | `bool` | | `date` | `date` | | `datetime` | `date` | | `reference` | `object` | | `reference` > `user` | `user` | When you import a Salesforce `email` field, you can choose to map it either as a `string` or as a `user`. When mapped as `user`, Qobra matches the email value against the email addresses of the users of your Qobra company: * If a user matches, the record is linked to that user, and the cell displays the user's avatar and name. * If no user matches, the email value is kept as-is in the record, and the cell displays *Not imported*. Hovering over the cell shows a tooltip indicating that this email does not match any user of your company. The match is exact and case-sensitive. Qobra integration supports multi-currency synchronization, based on the `CurrencyIsoCode` field, present in each Salesforce object. Custom queries are based on SOQL where conditions. For instance, you could add as a custom query `StageName = 'Closed Won’` to filter your Opportunity object. ## Polymorphic References Salesforce uses polymorphic references (also known as "polymorphic lookup fields" or "WhoId/WhatId fields") in certain objects like Events and Tasks. These special reference fields can point to multiple types of entities. For example: * The `WhoId` field can reference Leads, Contacts, or Users * The `WhatId` field can reference Accounts, Opportunities, Cases, and other custom objects Unfortunately, Qobra does not currently support polymorphic references due to their complex nature. When synchronizing objects that contain these fields, the references will not be properly resolved. ### Workarounds To handle this limitation, here are some suggested workarounds: 1. **Create Separate Fields**: Instead of using the polymorphic fields, you can create separate lookup fields for each type of entity you need to reference. For example: * `Account__c` * `Opportunity__c` * `Lead__c` Then use Process Builder or Flow to automatically populate these fields based on the WhatId/WhoId values. 2. **Use Record Types**: Create different record types for your Events/Tasks based on the type of entity they're related to. This allows you to have different page layouts and field sets for each type of relationship. 3. **Custom Junction Objects**: For complex relationships, consider creating custom junction objects that explicitly define the relationships between your records. [Contact Qobra support](mailto:support@qobra.co) for more information. # Zoho Source: https://docs.qobra.co/integration_documentation/crms/zoho ## Introduction Qobra connects to Zoho through an application, which needs to be installed via an Oauth check. The integration is used to synchronize tables from Zoho, with their fields and their content. This integration is readonly and does not write anything in your Zoho. ## Access ### Oauth authentication & scopes To connect your Pipedrive to Qobra, you’ll need to go through an Oauth flow, granting us access to several scopes: * `ZohoCRM.users.READ` (Read user information) * `ZohoCRM.modules.READ` (Read all modules information) * `ZohoCRM.settings.fields.READ` (Read settings) You’ll find more information about Zoho application scopes [here](https://www.zoho.com/crm/developer/docs/api/v6/scopes.html). ## Synchronization ### Refresh Frequency To ensure your Zoho CRM data stays current in Qobra, we follow this synchronization schedule: * **Daily Full Refresh**: Every night, we perform a complete sync of all your Zoho data * **Bi-hourly Updates**: Every 2 hours throughout the day, we sync any recent changes * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings ### Incremental synchronization This integration is an incremental synchronization. This means that all imports are based on each record last modified date, and that we only refresh what has been modified in the system since the previous import. When an import is launched, we thus refresh all records that have been modified since the start of the last successful import. Once we have refresh range, synchronization work is split and parallelized in smaller tasks for performance enhancement. The synchronization tasks are split according to the last modified date. It is important to note that Qobra admin can manually trigger complete synchronization in the application. ### Supported tables You’ll find here all tables supported by the Zoho integration: | **Zoho table** | **Name field** | | - | - | | `Deals` | `Deal_Name` | | `Leads` | `Full_Name` | | `Invoices` | `Invoice_Number` | | `Accounts` | `Account_Name` | | `Contacts` | `Full_Name` | | `Events` | `Event_Title` | ### Supported types You’ll find below the fields types supported by the Qobra integration, and their mapping to Qobra types. | **Zoho type** | **Qobra type** | | - | - | | `double` | `number` | | `integer` | `number` | | `currency` | `amount` | | `text` | `string` | | `textarea` | `string` | | `date` | `date` | | `datetime` | `date` | | `picklist` | `picklist` | | `userlookup` | `user` | | `ownerlookup` | `user` | | `formula` | `string`, `number`, `amount`, `percentage`, `date` | # Bigquery Source: https://docs.qobra.co/integration_documentation/databases/bigquery ## Introduction Qobra connects to Bigquery through google service accounts. This means that Qobra will only be able to access the data available to the service account. If you want to limit access to specific tables, you should restrict the service account's permissions accordingly. Although a service account is tied to a project, it does not automatically have access to all the data within that project. You can control its access by configuring IAM roles and permissions to limit it to specific datasets and tables. **Note**: Qobra will still be able to view the names of all tables and routines within a dataset, even if it does not have permission to access their contents. The integration is used to synchronize exposed tables, their columns and their content. This integration is readonly and does not write anything in your Bigquery database. ## Setup 1. Create a service account from your google account, with the following permissions: * `bigquery.jobs.create` * `bigquery.tables.get` * `bigquery.tables.list` * `bigquery.routines.get` * `bigquery.routines.list` * `bigquery.tables.getData` (optional) You'll find more information about creating service account [here](https://cloud.google.com/iam/docs/service-accounts-create). In the end, your service account should look something like this: ```json theme={null} { "type": "service_account", "project_id": "project-id", "private_key_id": "key-id", "private_key": " - - -BEGIN PRIVATE KEY - - -\nprivate-key\n - - -END PRIVATE KEY - - -\n", "client_email": "service-account-email", "client_id": "client-id", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://accounts.google.com/o/oauth2/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/service-account-email" } ``` 2. You’ll then be able to fill in connection modal in app: * `Service account`: Service account * `Dataset`: Bigquery dataset Bigquery setup in Qobra 3. (optional) If the data feeding your table is coming from a Google Spreadsheet, you'll need the `bigquery.tables.getData` permission. In the source spreadsheet, you'll also need to give a read access to the service account's email address. ## Synchronization ### Table requirements To synchronize a file to Qobra, it must meet requirements: * the file contains a **unique identifier** column * the file contains a **name** column * the user values contained in the file must be emails by default, but it can be changed in app at table creation ### Total synchronization This integrations does total synchronization, meaning the whole document will be synced at every refresh. ### Refresh Schedule By default, we perform one Full Refresh per day to keep your data up to date. However, you can customize both the frequency and timing of these refreshes to better suit your needs: * **Default Schedule**: one Full Refresh per day * **Customizable Options**: * Adjust the number of daily refreshes * Set specific hours for the refreshes to occur * Configure the schedule through your integration settings * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings Refresh schedule setup in Qobra ### Supported types You’ll find below the fields types supported by the Qobra integration, and their mapping to Qobra types. | **Bigquery type** | **Qobra type** | | - | - | | `STRING` | `string`, `picklist`, `multipicklist`, `user` | | `INT64` | `number`, `amount`, `percentage`, `user`, `picklist`, `multipicklist` | | `FLOAT64` | `number`, `amount`, `percentage` | | `NUMERIC` | `number`, `amount`, `percentage` | | `TIMESTAMP` | `date` | | `DATE` | `date` | | `DATETIME` | `date` | | `BOOL` | `bool` | ### Custom querying With this integration, you can apply a custom query to your table synchronization, to avoid synchronizing too much information, consequently making synchronization quicker. Custom queries are based on SQL where conditions. For instance, you could add as a custom query `StageName = 'Closed Won’` to filter your Opportunity object. Contact support for more information. # PostgreSQL Source: https://docs.qobra.co/integration_documentation/databases/postgresql ## Introduction Qobra can connect to PostgreSQL by two means: * username / password credentials. * service account (only available for Google Cloud Platform) The integration is used to synchronize exposed tables, their columns and their content. This integration is readonly and does not write anything in your PostgreSQL database. ## Setup via SQL user 1. Create an account to setup the Qobra connection, ensuring the principle of least privilege. This means the account needs to have read only access, on table that need to be synchronized with Qobra. You’ll find Postgre’s documentation regarding User, role, & privilege commands [here](https://www.postgresql.org/docs/8.0/user-manag.html). Once your account is created, make sure you keep its username and password for later steps. 2. Whitelist Qobra’s IP: `13.39.2.144` 3. You’ll then be able to fill in connection modal in app: * `Username`: PostgreSQL account username * `Password`: PostgreSQL account password * `Host`: PostgreSQL account’s unique URL * `Port`: by default 5432 * `Database`: PostgresSQL database PostgreSQL setup via SQL user ## Setup via service account 1. Create a service account from your google account, it should look something like this: ```json theme={null} { "type": "service_account", "project_id": "project-id", "private_key_id": "key-id", "private_key": " - - -BEGIN PRIVATE KEY - - -\nprivate-key\n - - -END PRIVATE KEY - - -\n", "client_email": "service-account-email", "client_id": "client-id", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://accounts.google.com/o/oauth2/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/service-account-email" } ``` 2. You’ll then be able to fill in connection modal in app: * `Username`: PostgreSQL account username * `Password`: PostgreSQL account password * `Service account`: PostgreSQL account’s unique URL * `Instance`: PostgresSQL instance * `Database`: PostgresSQL database PostgreSQL setup via service account ### Table requirements To synchronize a file to Qobra, it must meet requirements: * the file contains a **unique identifier** column * the file contains a **name** column * the user values contained in the file must be emails by default, but it can be changed in app at table creation ### Total synchronization This integrations does total synchronization, meaning the whole document will be synced at every refresh. ### Refresh Schedule By default, we perform one Full Refresh per day to keep your data up to date. However, you can customize both the frequency and timing of these refreshes to better suit your needs: * **Default Schedule**: one Full Refresh per day * **Customizable Options**: * Adjust the number of daily refreshes * Set specific hours for the refreshes to occur * Configure the schedule through your integration settings * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings Refresh schedule setup in Qobra ### Supported types You’ll find below the fields types supported by the Qobra integration, and their mapping to Qobra types. | **Postgres type** | **Qobra type** | | - | - | | `TEXT` | `string`, `picklist`, `multipicklist`, `user` | | `CHARACTER` | `string`, `picklist`, `multipicklist`, `user` | | `CHARACTER VARYING` | `string`, `picklist`, `multipicklist`, `user` | | `DATE` | `date` | | `TIMESTAMP WITHOUT TIME ZONE` | `date` | | `TIMESTAMP WITH TIME ZONE` | `date` | | `INTEGER` | `number`, `amount`, `percentage`, `user` | | `SMALLINT` | `number`, `amount`, `percentage`, `user` | | `BIGINT` | `number`, `amount`, `percentage`, `user` | | `DOUBLE PRECISION` | `number`, `amount`, `percentage` | | `NUMERIC` | `number`, `amount`, `percentage` | | `FLOAT` | `number`, `amount`, `percentage` | | `REAL` | `number`, `amount`, `percentage` | | `MONEY` | `amount` | | `BOOLEAN` | `bool` | | `BIT` | `string` | | `BIT VARYING` | `string` | ### Custom querying With this integration, you can apply a custom query to your table synchronization, to avoid synchronizing too much information, consequently making synchronization quicker. Custom queries are based on SQL where conditions. For instance, you could add as a custom query `StageName = 'Closed Won’` to filter your Opportunity object. Contact support for more information. # snowflake Source: https://docs.qobra.co/integration_documentation/databases/snowflake The integration is used to synchronize exposed tables, their columns, and their content. It is read-only and does not write anything to your Snowflake database. ## Setup 1. Create a dedicated account to set up the Qobra connection, following the principle of least privilege. This means the account should have read-only access to the tables that need to be synchronized with Qobra. You’ll find Snowflake’s documentation regarding *User, role, and privilege commands* [here](https://docs.snowflake.com/en/sql-reference/commands-user-role). 2. Whitelist Qobra’s IP address: `13.39.2.144` (infra EU) or `18.206.233.133` (infra US). 3. Configure key-pair authentication to enable the Snowflake integration within the Qobra application. We recommend following the official [Snowflake documentation](https://docs.snowflake.com/en/user-guide/key-pair-auth#configuring-key-pair-authentication) to correctly generate and configure the key pair required to complete the connection. 4. You’ll then be able to fill in the connection modal in app: * `Username`: Snowflake account username * `Host`: Snowflake account’s unique URL * `Port`: by default `443` for encrypted (TLS/SSL) connections * `Database`: Snowflake database * `SSH private key`: The private SSH key generated at step 3 Snowflake setup in Qobra ## Synchronization | **Snowflake type** | **Qobra type** | | - | - | | `TEXT` | `string`, `picklist`, `multipicklist`, `user` | | `NUMBER` | `number`, `amount`, `percentage`, `user` | | `FLOAT` | `number`, `amount`, `percentage` | | `DATE` | `date` | | `TIMESTAMP_TZ` | `date` | Custom queries are based on SQL where conditions. For instance, you could add as a custom query `StageName = 'Closed Won’` to filter your Opportunity object. Contact support for more information. # NetSuite Source: https://docs.qobra.co/integration_documentation/erp/netsuite ## Introduction Qobra connects to NetSuite with Token-Based Authentication (TBA), through the SuiteTalk REST web services. The integration is used to synchronize your NetSuite records into Qobra data tables, and optionally to import your users from NetSuite employees. It is read-only and does not write anything to your NetSuite account. ## Setup The setup is done by a NetSuite administrator. ### 1. Enable the features Go to **Setup > Company > Enable Features**: * **SuiteCloud** subtab: check **REST Web Services** and **Token-Based Authentication**. * **Analytics** subtab: check **SuiteAnalytics Workbook**. ### 2. Create a role for Qobra Go to **Setup > Users/Roles > Manage Roles > New** and create a role dedicated to Qobra, with the following permissions: | **Subtab** | **Permission** | **Level** | | - | - | - | | Setup | Log in using Access Tokens | Full | | Setup | REST Web Services | Full | | Reports | SuiteAnalytics Workbook | Edit | | Transactions, Lists | Each record you want to import in Qobra | View | For instance, add `Sales Order`, `Invoice` or `Customer` with the View level if you want to import these records. Do not use the Administrator role: Oracle advises against it for web services integrations. See NetSuite's documentation: [Prerequisites and Setup for REST Web Services](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/article_5085602973.html). #### User import permissions These permissions are optional: add them to the role only if you want to import your users from NetSuite employees. | **Subtab** | **Permission** | **Level** | | - | - | - | | Lists | Employees | View | | Lists | Employee Record | View | | Lists | Perform Search | View | Without them, Qobra cannot read your employees and the user import from NetSuite stays empty. See NetSuite's documentation: [Employee record in REST web services](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_161886175135.html). ### 3. Create a user for Qobra Create a user dedicated to Qobra (**Lists > Employees > New**). On its **Access** subtab, give it access to NetSuite and assign it the role created at step 2. ### 4. Create an integration record Go to **Setup > Integration > Manage Integrations > New**, name it `Qobra`, and under **Authentication** check **Token-Based Authentication**. Save: the **Consumer Key** and **Consumer Secret** are displayed. See NetSuite's documentation: [Create Integration Records for Applications to Use TBA](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/bridgehead_4249032125.html). ### 5. Create an access token Go to **Setup > Users/Roles > Access Tokens > New**, and select the `Qobra` application, the user created at step 3 and the role created at step 2. Save: the **Token ID** and **Token Secret** are displayed. See NetSuite's documentation: [Access Token Management – Create and Assign a TBA Token](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/bridgehead_4254081947.html). NetSuite displays the consumer and token secrets only once, at creation. Copy them right away: if you lose them, you will have to generate new ones. ### 6. Fill in the connection modal in Qobra The base URL and the account ID are both on **Setup > Company > Company Information**. | **Field** | **Where to find it** | | - | - | | `Base URL` | **Company URLs** subtab, **SuiteTalk** URL | | `Account ID` | **Account ID** field | | `Consumer key` | Integration record, step 4 | | `Consumer secret` | Integration record, step 4 | | `Token ID` | Access token, step 5 | | `Secret token` | Access token, step 5 | The `Base URL` follows the format `https://[ACCOUNT-ID].suitetalk.api.netsuite.com`. See NetSuite's documentation: [URLs for Account-Specific Domains](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_1498251763.html). For a sandbox, the account ID is written differently in the two fields: * `Base URL`: lowercase with a hyphen, e.g. `https://1234567-sb1.suitetalk.api.netsuite.com` * `Account ID`: uppercase with an underscore, e.g. `1234567_SB1` The `Base URL` is never the URL of the NetSuite interface (`app.netsuite.com`). [Contact Qobra support](mailto:support@qobra.co) for more information. # S3 Source: https://docs.qobra.co/integration_documentation/files/s3 ## Introduction Qobra can connect to your AWS account and synchronize `csv` files from an S3 bucket. This integration is readonly and will not write anything in your S3 buckets. ## Access & setup 1. Create the S3 bucket that will hold your synchronized `csv` files. 2. Create an AWS policy with the least access needed for the integration to work: * `s3:ListBucket`: lists S3 buckets (among restricted resources) * `s3:GetObject`: retrieves documents from S3 buckets (among restricted resources) The policy should look something like this: ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::qobra-integration-bucket", "arn:aws:s3:::qobra-integration-bucket/*" ] } ] } ``` Attach the policy to the IAM user or role to give us access to the S3 bucket. *If you are using resource based policy, make sure the bucket policy is updated accordingly.* 3. Fill in your bucket information in the application: S3 setup in Qobra ## Synchronization ### CSV file requirements To synchronize a file to Qobra, it must meet requirements: * the file has a `csv` format, with `;` separator * the file does not change name * the file contains a **unique identifier** column * the file contains a **name** column * the date values contained in the file must follow this format: `YYYY-MM-DD` * the user values contained in the file must be emails by default, but it can be changed in app at table creation ### Total synchronization This integrations does total synchronization, meaning the whole document will be synced at every refresh. ### Refresh Schedule By default, we perform one Full Refresh per day to keep your data up to date. However, you can customize both the frequency and timing of these refreshes to better suit your needs: * **Default Schedule**: one Full Refresh per day * **Customizable Options**: * Adjust the number of daily refreshes * Set specific hours for the refreshes to occur * Configure the schedule through your integration settings * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings Refresh schedule setup in Qobra # SFTP Source: https://docs.qobra.co/integration_documentation/files/sftp ## Introduction Qobra can connect and synchronize `csv` files thanks to the SFTP protocol. ⚠️ You are pushing files on Qobra's SFTP server. Only one SFTP is allowed for a company. ## SSH key generation You can write data in our SFTP server using SSH Key pair as authentication. You authenticate using your private key on your machine and we use your public key to verify the sender. When you generate an SSH key, you can add a passphrase to further secure the key. If so, you must enter the passphrase each time you use the key. If you don't already have an SSH key, you must generate a new SSH key to use for authentication. You can generate a new SSH key on your machine. After you generate the key, keep the public part of the key to provide it to Qobra later on. Key type allowed are `rsa`, `ed25519`, `sha2` and `ecdsa`. 1. Open Terminal. 2. Paste the text below, replacing the email used in the example with your qobra user email address. ```bash theme={null} ssh-keygen -t ed25519 -C "your_email@example.com" ``` or ```bash theme={null} ssh-keygen -t rsa -b 4096 -C "your_email@example.com" ``` When you're prompted to "Enter a file in which to save the key", you can press Enter to accept the default file location. 3. At the prompt, type a secure passphrase if you want to. If you don't want to have a passphrase just press enter. ## Setup in Qobra 1. Create the SSH key that will authenticate you when you send Qobra files via SFTP. Keep the public part of the key to provide it to Qobra later on. Be careful, Qobra only lets you use a single SSH key for all synchronized files with SFTP. Make sure that every data source that will push to our SFTP server use the same key. 2. Fill in the integration modal with your SSH public key. SFTP setup in Qobra 3. Push your `csv` files via SFTP, with the SSH key you created in step 1. ## Synchronization ### CSV file requirements To synchronize a file to Qobra, it must meet requirements: * the file has a `csv` format, with `;` separator * the file does not change name * the file contains a **unique identifier** column * the file contains a **name** column * the date values contained in the file must follow this format: `YYYY-MM-DD` * the user values contained in the file must be emails by default, but it can be changed in app at table creation ### Total synchronization This integrations does total synchronization, meaning the whole document will be synced at every refresh. ### Refresh Schedule By default, we perform one Full Refresh per day to keep your data up to date. However, you can customize both the frequency and timing of these refreshes to better suit your needs: * **Default Schedule**: one Full Refresh per day * **Customizable Options**: * Adjust the number of daily refreshes * Set specific hours for the refreshes to occur * Configure the schedule through your integration settings * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings Refresh schedule setup in Qobra # HRIS Connector Source: https://docs.qobra.co/integration_documentation/hris/hris_connector ## Introduction Qobra connects seamlessly to 60+ HRIS, including `Workday`, `Lucca`, `Factorial`, `ADP`, `Sage` and many more. You can access the whole list [here](https://www.kombo.dev/integrations). The integration is used to: * synchronize users, management hierarchy, user groups, user attributes * auto-provision users ## Sub-processing and security To connect to all these HRIS systems, we rely on Kombo, a german subprocessor. Kombo has been carefully audited on feature and security standards. They follow the best security standards, and are `ISO27001`, `SOC 2 type 2`, and `GDPR` compliant. You’ll find more information [here](https://security.kombo.dev/). ## Access For each HRIS, you will be able to: * choose the information scopes you give Qobra access to. * connect your HRIS to Qobra via the most secured way available. For most systems, this will be Oauth 2.0, but it can sometimes be via an api key or a service user as shown below. HRIS integration scope HRIS integration setup For the full walkthrough — setting up the connection, running a sync, and what each synchronized field maps to — see the Help Center article: [HRIS integration](https://help.qobra.co/en/articles/14430251-hris-integration). # Introduction Source: https://docs.qobra.co/integration_documentation/introductions/inbound_integration Qobra works by processing data, which we divide into two types: * **User data**, meaning data about your employees, the management structure of your organisation, groups of employees … * **Data tables** that are necessary to calculate compensations (for instance sales opportunities, quota and objectives, invoicing data, …). Those data tables represent any data that needs to be crunched to get your compensation amounts right. ## Synchronize user data User data can be synchronized several ways: * with [your HRIS](/integration_documentation/hris/hris_connector) (Workday, ADP, Lucca, Factorial, Sage ...) * via Qobra’s APIs Synchronize user data ### API endpoints To synchronize data in Qobra, you’ll need: * the [Upsert users bulk](/api_reference/v1/endpoints/users/upsert_bulk_user) route, to upsert users in bulk * the [Upsert group attributions bulk](/api_reference/v1/endpoints/users/upsert_bulk_user_group) route, to assign each user a group * the [Upsert manager attributions bulk](/api_reference/v1/endpoints/users/upsert_bulk_user_manager) route, to assign each user a manager * the [Archive users bulk](/api_reference/v1/endpoints/users/archive_users) route, to archive users ## Synchronize data tables Data tables can be synchronized several ways: * with CRM integrations ([Salesforce](/integration_documentation/crms/salesforce), [Hubspot](/integration_documentation/crms/hubspot), [Pipedrive](/integration_documentation/crms/pipedrive), [Microsoft Dynamics](/integration_documentation/crms/microsoft_dynamics), [Zoho CRM](/integration_documentation/crms/zoho), [Odoo](/integration_documentation/crms/odoo)) * with Database & datalake integrations ( [Snowflake](/integration_documentation/databases/snowflake), [Bigquery](/integration_documentation/databases/bigquery), [PostgreSQL](/integration_documentation/databases/postgresql)) * with File transfer integrations ([S3](/integration_documentation/files/s3), [SFTP](/integration_documentation/files/sftp)) * via Qobra’s APIs Synchronize data tables ### API endpoints To synchronize data tables in Qobra, you’ll need: * the [Upsert records bulk](/api_reference/v1/endpoints/data_tables/ingest_records) route, to upsert records in bulk in a data table * the [Fetch import](/api_reference/v1/endpoints/imports/fetch_import) route, to check your import's status ### Refresh Frequency Qobra uses two types of data synchronization methods to keep your data up to date: 1. **Full Refresh**: A complete scan of all your data within the configured perimeter 2. **Differential Refresh**: A scan that only looks for changes since the last synchronization The refresh frequency varies by integration type: #### CRM Integrations * **Salesforce, Hubspot, PipeDrive, Microsoft Dynamics, Zoho** * Daily Full Refresh (during night hours) * Bi-hourly Updates * On-Demand Updates * **Odoo** * Bi-hourly Full Refresh * On-Demand Updates #### Database, Datalake, and File Transfer Integrations * **Snowflake, BigQuery, PostgreSQL, S3, SFTP** * Default: Four Full Refreshes per day * Customizable frequency and timing through the integration settings ## Read data with AI assistants (MCP) To read Qobra data from an AI assistant instead of syncing data in, use the [Qobra MCP server](/mcp_documentation/introduction). It exposes read-only tools over the Model Context Protocol (MCP) so Claude, ChatGPT, or Dust can query your Qobra workspace. # Introduction Source: https://docs.qobra.co/integration_documentation/introductions/outbound_integration There are two main ways to automatize data outputs from Qobra: * via the Webhook * via Qobra’s APIs Data outputs diagram ## Webhook In Qobra, you can send an HTTPS request when a statement is sent to payroll. This is mainly use to push data back to your payroll system if necessary. You’ll find the webhook documentation [here](/integration_documentation/webhook/webhook). ## APIs To extract Qobra’s data to your datalake, customers use ETLs (Airbyte, Fivetran) coupled with Qobra’s API. Here are the endpoints that should be considered: * the [Fetch statements records](/api_reference/v2/endpoints/data/fetch_statements) route (v2), to retrieve statement reporting data * the [Fetch reporting records](/api_reference/v2/endpoints/data/fetch_records) route (v2), to retrieve record reporting data * the [Fetch users](/api_reference/v1/endpoints/users/fetch_users) route (v1), to retrieve all user data * the [Fetch data table records](/api_reference/v1/endpoints/data_tables/fetch_records_data) route (v1), to retrieve all data tables data Reporting is served by the v2 API — the [v2 quickstart](/api_reference/v2/quickstart) walks through a full extraction. The v1 reporting routes still work but are deprecated: if your integration already uses them, follow the [migration guide](/api_reference/v2/migration_guide). User and data table endpoints stay on v1 and are maintained. # Webhook Source: https://docs.qobra.co/integration_documentation/webhook/webhook ## Introduction Qobra offers a webhook solution, to automatize processes triggered by statements lifecycle. This will enable you, for instance, to send a HTTP request when a statement is sent to payroll. Our customer’s main use case is to send commission amount to their payroll system when a statement is sent to payroll. ## Setup Webhook setup is pretty simple, you just have to enable it in app, choose the URL you want to send a request to. You’ll find a webhook secret, used to authenticate the request on the system receiving the call. Webhook setup modal The request sent: * is using the `POST` method * is secured thanks to the webhook secret, provided in a the `X-Webhook-Secret` header * contains all statement reporting records linked to the sent to payroll statement ## Track your calls You’ll find in Qobra a space to track all webhook calls individually, with all information necessary to troubleshoot (body, response, status, url, timestamp …). Webhook call visualization You also have the option to send back the request, if necessary. # Connect an MCP client to Qobra Source: https://docs.qobra.co/mcp_documentation/connection Prerequisites, the OAuth consent flow, and adding the MCP server to Claude, ChatGPT, Dust or n8n ## Prerequisites A Qobra admin controls access in **Settings > AI**. Before you connect, the admin must: 1. Turn on AI for the company. 2. Turn on the **MCP** agent. The MCP agent can run for all users or as a pilot. In pilot mode, only the users in the pilot group can connect. Ask your admin to add you to the pilot if you're not in it. If access is denied, your MCP client shows one of these messages: | Message | Fix | | - | - | | `AI is turned off for this company.` | An admin turns on AI in **Settings > AI**. | | `MCP is turned off for this company.` | An admin turns on the **MCP** agent in **Settings > AI**. | | `MCP is limited to a pilot group and you are not in it. Ask an admin to add you to the pilot.` | An admin adds you to the MCP pilot group, or switches the agent to all users. | You authenticate as yourself. The tools only ever return data your Qobra user is allowed to see — the same role-based permissions as the app. ## Server URL The server speaks **Streamable HTTP**. Qobra runs it in two regions — pick the one that matches where your Qobra workspace is hosted (the same region you log in to the app from). If you're not sure, ask the Qobra team. | Region | Server URL | | - | - | | Europe (EU) | `https://mcp.qobra.co/mcp` | | United States (US) | `https://mcp-us.qobra.co/mcp` | There is **no API key**. Every client authenticates through the same OAuth 2.1 flow described in [Authorize](#authorize-oauth-consent) below — you only ever paste the server URL, never a secret. Everywhere below uses the EU URL as the example. If your workspace is hosted in the US, substitute `https://mcp-us.qobra.co/mcp` throughout. ## Add the server to your client Open **Settings → Connectors → Add custom connector**, give it a name (e.g. `Qobra`) and paste the server URL (`https://mcp.qobra.co/mcp`). Leave the Advanced settings (OAuth Client ID / Secret) empty. Adding the Qobra MCP server as a custom connector in Claude Custom MCP connectors live behind **Developer mode**, available on **ChatGPT Plus, Pro, Business, Enterprise and Edu** plans. Open **Settings → Connectors → Advanced** (`chatgpt.com/apps#settings/Connectors/Advanced`) and click **Create** to open the **New App** dialog. Enter a **Name** (e.g. `Qobra`). The icon and description are optional. Under **Connection**, keep **Server URL** selected and paste `https://mcp.qobra.co/mcp`. Set **Authentication** to **OAuth**. You can leave the **Advanced OAuth settings** as discovered — the server registers the client automatically. Tick **I understand and want to continue** (the standard warning for any custom MCP server), then click **Create**. Adding the Qobra MCP server as a custom connector in ChatGPT Add Qobra as a **remote MCP server** from a Dust space. Open your space's **Tools** (the *Actions* category), then **Add MCP Server** to open the **Configure MCP Server** dialog. In **URL**, paste `https://mcp.qobra.co/mcp`. Keep **Authentication** set to **Automatic**. Dust discovers that the server requires OAuth and uses dynamic client registration to obtain the credentials. Leave **Use custom headers** off. Click **Save**, complete the OAuth consent, then add the Qobra tools to an agent so it can query your Qobra data. Adding the Qobra MCP server as a remote MCP server in Dust Create an **MCP OAuth2 API** credential and turn on **Dynamic Client Registration**. Paste the server URL (`https://mcp.qobra.co/mcp`). Set the scope to `mcp:read`. Do not use `openid`. See [Scopes](#scopes). Save the credential and complete the OAuth consent. ## Authorize (OAuth consent) The server is protected by **OAuth 2.1 + PKCE** — there is no API key to copy. The flow is identical for every client (Claude, ChatGPT, Dust, n8n). On first use: The client discovers the server's OAuth metadata and registers itself automatically. The client opens the Qobra consent screen in your browser. Log in to Qobra as usual (SSO / MFA included) and click **Authorize**. The client receives a short-lived token and the Qobra tools appear in its toolbox. Tokens are scoped to your user and company, and can be revoked. ### Scopes The server has a single OAuth scope: `mcp:read`. It publishes this scope in `scopes_supported` in both its protected-resource and authorization-server metadata. * Every client gets `mcp:read`, including clients that register without a scope. The `/token` response always returns `scope: "mcp:read"`. * If your client asks for scopes, request `mcp:read` or no scope at all. * Do not request `openid`. The server doesn't support OpenID Connect and doesn't issue an `id_token`. Any scope other than `mcp:read` fails with `invalid_scope`. If a client fails to connect with `invalid_scope`, reconnect it so it rediscovers the server's scope. You don't need to recreate the credential or connector. # Qobra MCP server Source: https://docs.qobra.co/mcp_documentation/introduction Connect Claude, ChatGPT or Dust to your Qobra data through the Model Context Protocol The Qobra MCP server is enabled **per company**. A Qobra admin turns on AI and the **MCP** agent in **Settings > AI**. If the MCP agent is in pilot mode, only users in the pilot group can connect. Every tool is scoped to your Qobra user's permissions. The **Qobra MCP server** exposes a set of tools over the [Model Context Protocol](https://modelcontextprotocol.io), so an MCP client like **Claude**, **ChatGPT** or **Dust** can answer questions about your Qobra workspace and help build a demo end to end straight from a conversation. It covers data tables, integrations, users, roles, groups, quotas, compensation plans, commission letters, reports, requests, statements, imports, audit logs, Sales Coach discussions, Help Center articles and sandboxes, plus an impact analysis of everything that depends on a resource. ## What these pages document Each tool page describes a tool's **inputs** and the **shape of what it returns**. These are **MCP tools, not REST endpoints**: you never call them over HTTP yourself — you connect an MCP client (Claude, ChatGPT or Dust) to the server, and the model calls the tools for you. The `POST /mcp/tools/` shown on each page is only how we render a tool's request and response schema. ## Available tools Read tools are always exposed, scoped to the authenticated user's company and role-based permissions. Write tools are gated separately — see [Write tools (beta)](#write-tools-beta) below. | Domain | Tools | | - | - | | Identity | [`get_identity`](/mcp_documentation/tools/get_identity), [`get_company_settings`](/mcp_documentation/tools/get_company_settings) | | Sandboxes | [`list_sandboxes`](/mcp_documentation/tools/list_sandboxes) | | Data tables | [`list_data_tables`](/mcp_documentation/tools/list_data_tables), [`get_data_table`](/mcp_documentation/tools/get_data_table), [`list_data_table_records`](/mcp_documentation/tools/list_data_table_records), [`create_data_table`](/mcp_documentation/tools/create_data_table), [`create_data_table_field`](/mcp_documentation/tools/create_data_table_field), [`create_record`](/mcp_documentation/tools/create_record), [`update_record`](/mcp_documentation/tools/update_record), [`split_record`](/mcp_documentation/tools/split_record) | | Integrations | [`list_integrations`](/mcp_documentation/tools/list_integrations) | | Users | [`list_users`](/mcp_documentation/tools/list_users), [`get_user`](/mcp_documentation/tools/get_user), [`list_user_attributes`](/mcp_documentation/tools/list_user_attributes), [`create_user_attribute`](/mcp_documentation/tools/create_user_attribute) | | Roles | [`list_roles`](/mcp_documentation/tools/list_roles), [`get_role`](/mcp_documentation/tools/get_role) | | Groups | [`list_groups`](/mcp_documentation/tools/list_groups), [`get_group`](/mcp_documentation/tools/get_group), [`list_group_dimensions`](/mcp_documentation/tools/list_group_dimensions), [`create_group`](/mcp_documentation/tools/create_group), [`update_group`](/mcp_documentation/tools/update_group), [`create_group_dimension`](/mcp_documentation/tools/create_group_dimension) | | Quotas | [`list_quotas`](/mcp_documentation/tools/list_quotas), [`list_quota_values`](/mcp_documentation/tools/list_quota_values), [`create_quota`](/mcp_documentation/tools/create_quota), [`update_quota_values`](/mcp_documentation/tools/update_quota_values) | | Plans | [`list_plans`](/mcp_documentation/tools/list_plans), [`get_plan`](/mcp_documentation/tools/get_plan), [`create_plan`](/mcp_documentation/tools/create_plan), [`update_plan`](/mcp_documentation/tools/update_plan), [`create_compensation`](/mcp_documentation/tools/create_compensation) | | Commission letters | [`list_commission_letters_campaigns`](/mcp_documentation/tools/list_commission_letters_campaigns), [`list_commission_letters`](/mcp_documentation/tools/list_commission_letters), [`get_commission_letters_campaign`](/mcp_documentation/tools/get_commission_letters_campaign) | | Reports | [`list_reports`](/mcp_documentation/tools/list_reports), [`get_report`](/mcp_documentation/tools/get_report), [`get_report_data`](/mcp_documentation/tools/get_report_data), [`get_report_pivot_data`](/mcp_documentation/tools/get_report_pivot_data), [`get_report_chart_data`](/mcp_documentation/tools/get_report_chart_data), [`create_report`](/mcp_documentation/tools/create_report) | | Dashboards | [`list_dashboards`](/mcp_documentation/tools/list_dashboards), [`get_dashboard`](/mcp_documentation/tools/get_dashboard), [`create_dashboard`](/mcp_documentation/tools/create_dashboard), [`update_dashboard`](/mcp_documentation/tools/update_dashboard) | | Requests | [`list_requests`](/mcp_documentation/tools/list_requests), [`get_request`](/mcp_documentation/tools/get_request), [`create_request`](/mcp_documentation/tools/create_request), [`update_request`](/mcp_documentation/tools/update_request), [`answer_request`](/mcp_documentation/tools/answer_request) | | Sales Coach | [`list_sales_coach_discussions`](/mcp_documentation/tools/list_sales_coach_discussions), [`get_sales_coach_discussion`](/mcp_documentation/tools/get_sales_coach_discussion) | | Statements | [`list_statements`](/mcp_documentation/tools/list_statements), [`get_statement`](/mcp_documentation/tools/get_statement), [`get_statement_compensation`](/mcp_documentation/tools/get_statement_compensation), [`list_statement_records`](/mcp_documentation/tools/list_statement_records), [`list_statement_periods`](/mcp_documentation/tools/list_statement_periods), [`list_exchange_rates`](/mcp_documentation/tools/list_exchange_rates), [`update_exchange_rates`](/mcp_documentation/tools/update_exchange_rates) | | Imports | [`list_imports`](/mcp_documentation/tools/list_imports), [`get_import`](/mcp_documentation/tools/get_import) | | Audit logs | [`list_audit_logs`](/mcp_documentation/tools/list_audit_logs), [`get_audit_log`](/mcp_documentation/tools/get_audit_log) | | Impact analysis | [`list_dependencies`](/mcp_documentation/tools/list_dependencies) | | Help Center | [`list_help_center_articles`](/mcp_documentation/tools/list_help_center_articles), [`get_help_center_article`](/mcp_documentation/tools/get_help_center_article) | ### Reading a sandbox Every tool reads your company's **live environment** (its real compensation data) by default. If your company uses sandboxes and your role can read them, call [`list_sandboxes`](/mcp_documentation/tools/list_sandboxes) to discover them, then pass a sandbox's id as the `sandbox_id` argument of a list tool (`list_plans`, `list_quotas`, `list_statements`, `list_statement_periods`, `list_reports`, `list_dashboards`) to read that sandbox instead. `sandbox_id` only accepts a sandbox's id — never a sandbox name, and never the live environment's own id (omit the argument to read the live environment). Tools that take a resource id — such as `get_plan`, `get_statement` or `get_report` — need no `sandbox_id` argument: the id itself names the environment the resource lives in. The write tools that create an environment-scoped resource (`create_quota`, `create_plan`, `create_report`, `create_dashboard`) accept the same `sandbox_id` argument to say which environment the new resource lands in. Omit it to write to the live environment. `create_compensation` does not: which environment it lands in is decided by the plan it hangs off. ## Write tools (beta) The MCP server also exposes a family of **`create_*` write tools**, plus `update_group`, `update_plan`, `update_quota_values`, `update_exchange_rates`, `update_dashboard`, `update_request` and `answer_request`, so an MCP client can build a demo end to end from a conversation. This surface is **in beta and enabled per company** — where it is not enabled, the write tools are hidden at discovery, and Qobra refuses any attempt to call one with a message that says the beta is not enabled for the company. Read tools are unaffected. Every write tool is scoped to the caller's role-based permissions in the same way a read tool is — a user without write access on plans cannot call `create_plan`, exactly as they cannot create a plan from the web app. **Every write is recorded in the audit trail**, as the `Qobra MCP` agent, so a change made by a model shows up in the trail alongside changes made from the web app and by other integrations. The MCP audits writes to the **live environment** only, mirroring the audit trail's own behavior — a write into a sandbox is not audited (the trail scopes to live). The write surface today: | Domain | Tools | | - | - | | Data tables | [`create_data_table`](/mcp_documentation/tools/create_data_table), [`create_data_table_field`](/mcp_documentation/tools/create_data_table_field), [`create_record`](/mcp_documentation/tools/create_record), [`update_record`](/mcp_documentation/tools/update_record), [`split_record`](/mcp_documentation/tools/split_record) | | Groups | [`create_group`](/mcp_documentation/tools/create_group), [`update_group`](/mcp_documentation/tools/update_group), [`create_group_dimension`](/mcp_documentation/tools/create_group_dimension) | | Quotas | [`create_quota`](/mcp_documentation/tools/create_quota), [`update_quota_values`](/mcp_documentation/tools/update_quota_values) | | Plans | [`create_plan`](/mcp_documentation/tools/create_plan), [`update_plan`](/mcp_documentation/tools/update_plan), [`create_compensation`](/mcp_documentation/tools/create_compensation) | | Reports | [`create_report`](/mcp_documentation/tools/create_report) | | Dashboards | [`create_dashboard`](/mcp_documentation/tools/create_dashboard), [`update_dashboard`](/mcp_documentation/tools/update_dashboard) | | Requests | [`create_request`](/mcp_documentation/tools/create_request), [`update_request`](/mcp_documentation/tools/update_request), [`answer_request`](/mcp_documentation/tools/answer_request) | | Users | [`create_user_attribute`](/mcp_documentation/tools/create_user_attribute) | | Exchange rates | [`update_exchange_rates`](/mcp_documentation/tools/update_exchange_rates) | Each `create_*` tool creates an **empty** resource — a plan with no compensation, a quota with no target values, a data table field with no values on any record. Configuring the resource further (loading values, attaching metrics) is done from the Qobra web app, with a few exceptions: `create_dashboard` can add reports to the new dashboard and arrange its tiles in the same call, `update_dashboard` renames a dashboard, adds reports to it and arranges its tiles, `update_plan` declares a plan's membership in one call, `update_quota_values` sets a quota's target values, `update_exchange_rates` sets the rates of one exchange-rate range, `create_record` adds one record to a data table with its values filled in, `update_record` changes or archives an existing record, `split_record` splits a record between users, `create_request` opens a request with its statements, members and assignee, and `update_request` changes, closes or reopens a request. Removing a tile from a dashboard is still done from the Qobra web app. Prerequisites, the OAuth consent flow, and how to add the server to Claude, ChatGPT or Dust. # answer_request Source: https://docs.qobra.co/mcp_documentation/tools/answer_request Post a message in a request's thread, as the caller — the natural second half of create_request, and the way an admin answers a contested commission. Post a message in a request's thread, as the caller. The natural second half of [`create_request`](/mcp_documentation/tools/create_request), and the way an admin answers a rep who contested their commission. Only an open request accepts a message: a closed one is refused, saying so. A request you can't access is reported as not found. Attachments are not supported here. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Id of the request to answer — the id list\_requests returns, the one create\_request just returned, or the one in a statement's `linked_requests`. The message to post, as plain text, as in the web app: anything between `<` and `>` is removed, so write no HTML and spell comparisons out in words. It is written in the caller's name and notifies every member of the request. ## Response The request the message was added to, as it now stands. See [`list_requests`](/mcp_documentation/tools/list_requests) for the full field breakdown. The message as it now reads in the thread. The message as written, with every @-mention rendered as `@` instead of the raw user id the web app stores. The users @-mentioned in `content`, in order of first appearance; empty when the message mentions nobody. # create_compensation Source: https://docs.qobra.co/mcp_documentation/tools/create_compensation Create a compensation — one commission rule — on a plan. The compensation starts empty and in draft; no statement changes until it is published. Create a compensation — one commission rule — on a compensation plan. The compensation is created empty and in draft: no scope, no variable, no formula, no payment rule. It shows up on the plan's design view and changes nobody's statement until it is configured and published. Read it back with [`get_plan`](/mcp_documentation/tools/get_plan). Which environment it lands in comes from the plan, so a plan you can't access reads exactly like one that doesn't exist — a plan living in a sandbox you may not read included. There is no `sandbox_id` argument on this tool for the same reason. Available only where the Qobra write tools beta is enabled. Every write to the live environment is recorded in the audit trail as the `Qobra MCP` agent; writes to a sandbox are not audited. ## Parameters Id of the plan the compensation hangs off — the id list\_plans returns. The plan also decides which environment the compensation lands in, so there is no sandbox argument here. Display name of the compensation, as people will read it on the plan — 'New business commission', 'Q1 accelerator'. How often the compensation is calculated. Defaults to `EACH_MONTH`. YYYY-MM; the first month the compensation applies to. Pass it with `end_date` to bound the compensation to a window, or omit both to leave it recurring (applying every period). YYYY-MM; the last month the compensation applies to, included. Goes together with `start_date`. ## Response The compensation as it now exists — the same row get\_plan lists on the plan, so the id can be reused straight away. # create_dashboard Source: https://docs.qobra.co/mcp_documentation/tools/create_dashboard Create a dashboard, with reports already added and arranged on it, or empty. Create a dashboard — the page a set of reports is shown on — with its reports already added and arranged, or empty. It comes back with its id and a link to it; reuse that id rather than looking the dashboard up again, which can miss a creation this recent. Pass `reports` to add reports as the dashboard is created, so a board is one call rather than two; a report's `visualization_types` adds only its chart or only its pivot table — `list_reports` names what each report can render in its `available_visualizations`. Pass `layout_rows` to place them: name each tile by its `report_id`, the tiles having no `visualization_id` until this call creates them. Omit both for an empty dashboard, then use `update_dashboard` later. **Read the `outcome` on every `tile_changes` row before reporting success.** The dashboard is created either way: a report that could not be added leaves the page emptier than asked, not the call failed. Removing a tile is still a job for the Qobra web app. `layout` is the page as it was created, read back from the dashboard: where each new tile landed. Available only where the Qobra write tools beta is enabled. Every write to the live environment is recorded in the audit trail as the `Qobra MCP` agent; writes to a sandbox are not audited. ## Parameters Display name of the dashboard, as people will read it in Qobra — 'Q3 board review', 'EMEA performance'. Optional free-text note about what the dashboard is for. Reports to add to the new dashboard, each as every tile it can render unless its visualization\_types narrows it. Omit to create the dashboard empty. Id of the report to add, as list\_reports returns it. Which of its tiles to add: `chart`, `pivot_table`, or both. Omit to add every tile the report offers, which is what you want unless the caller asked for one of the two; list\_reports names what each report offers in its available\_visualizations. The dashboard's rows, top to bottom; each row lists one or more tiles left to right. An entry is a visualization\_id from get\_dashboard, or a report\_id — the report form is how you arrange tiles this same call is adding, since they have no visualization\_id until they exist, and it places every tile of that report. A row holds at most three charts or two pivot tables and is split when it overflows; a tile you leave out keeps its place, appended on a row of its own. Omit to leave the arrangement alone; pass an empty list to reset it, every tile on a row of its own. Id of the sandbox to create the dashboard in — the id list\_sandboxes returns. Omit it to create the dashboard in the live environment. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. ## Example Create a dashboard with the charts of two reports, side by side on one row, and the pivot table of a third report on a row below: ```json theme={null} { "name": "Q3 board review", "description": "Quarterly bookings and attainment", "reports": [ { "report_id": "665f1c2e8a4b3d0012a7eb01", "visualization_types": ["chart"] }, { "report_id": "665f1c2e8a4b3d0012a7eb02", "visualization_types": ["chart"] }, { "report_id": "665f1c2e8a4b3d0012a7eb03", "visualization_types": ["pivot_table"] } ], "layout_rows": [ ["665f1c2e8a4b3d0012a7eb01", "665f1c2e8a4b3d0012a7eb02"], ["665f1c2e8a4b3d0012a7eb03"] ] } ``` A `report_id` entry in `layout_rows` places every tile that report adds. A row holds at most three charts or two pivot tables, and Qobra splits a row that overflows. Read `layout` in the response to see where each tile landed. ## Response The dashboard as it now exists — the same row list\_dashboards returns, so the id can be reused straight away. Deep link to open this dashboard in the Qobra web app. What became of each report you passed, one row per tile: a report added as both a chart and a pivot table gets a row for each, and one that adds nothing gets a single row, with visualization\_type null. Read `outcome` on each before reporting success, since a call can succeed having added nothing you asked for. Empty when no reports were passed. What became of this report. "added" for a tile this call put on the dashboard; "already\_added" for one the dashboard already carried, which is how a re-sent call reads as the no-op it was; "not\_found" when no report of yours has that id; "no\_visualization" when the report has neither a chart nor a pivot table to show; "type\_not\_available" when it has tiles but none of the visualization\_types you asked for. One of: `added`, `already_added`, `not_found`, `no_visualization`, `type_not_available`. The report id you passed, echoed back as you spelled it, so the row matches your request even when report is null. The report it resolved to, named — null when it is not one you can read, since naming it would say a report you cannot see exists. The tile this row is about: chart or pivot\_table. Null when no single tile can be named — nothing was added at all. One of: `chart`, `pivot_table`. Why, as a sentence saying what to do next. Null for a tile that landed, there being nothing to act on. The dashboard's rows as they now stand, top to bottom, each tile left to right — read back from the dashboard, so it shows where every tile actually sits. A tile this call added lands on a row of its own at the end. Empty when the dashboard has no tile yet. Rows passed in layout\_rows are applied after the reports are added and come back as the store took them: a row holds at most three charts or two pivot tables and is split when it overflows, and a tile you left out keeps its place on a row of its own. The tiles on this row, left to right. Rows read top to bottom. Id of the tile, as get\_dashboard lists it. The report this tile renders. What the tile renders the report as: chart or pivot\_table. One of: `chart`, `pivot_table`. One row per layout entry you passed, saying what became of it. Empty when no layout\_rows were passed. What became of this layout entry. "placed" for a tile this call positioned; "already\_placed" when an earlier entry of the same layout already placed it, a tile sitting on one row only; "not\_found" when the entry names neither a tile of this dashboard nor a report on it. One of: `placed`, `already_placed`, `not_found`. The layout entry you passed, echoed back. Why, as a sentence saying what to do next. Null for an entry that placed its tile. # create_data_table Source: https://docs.qobra.co/mcp_documentation/tools/create_data_table Create a local data table — the structure records are then imported into. The table is created empty; add columns and load rows separately. Create a local data table — the structure records are then imported into. A data table is the root most of a compensation plan hangs off: fields, record reports, scopes and quotas all point at one. This creates the empty structure with its name field; add its other columns with [`create_data_table_field`](/mcp_documentation/tools/create_data_table_field), and load its rows by Excel import from the Qobra web app. The table matches each record's owner on the user's email address. Tables synced from an integration are created from that integration's settings instead, not here. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Display name of the table, as people will read it in Qobra — 'Deals', 'Subscriptions', 'Invoices'. The table's stable key: the identifier the public API addresses it by, distinct from the display name. Lowercase and underscore-separated, and unique across the company. Omit to derive it from the name ('Closed Deals' gives 'closed\_deals'). Label of the column holding each record's unique identifier in the source data. Defaults to 'Record ID'. Label of the column holding each record's name — what a record is listed as throughout Qobra. Defaults to 'Name'. ## Response The data table as it now exists — the same row list\_data\_tables returns, so the id can be reused straight away. Deep link to open this data table in the Qobra web app. One of: `local`, `integration`. Always `local` for a table created here. Null — a table created here has no source integration. The table's views. A just-created table has one: its `Default` view, created with the table. Pass its id as `view_id` to list\_data\_table\_records. The columns on the table. A just-created table has only its name field. Stable technical key for this field in Qobra's public API. Field data type. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. When type is object, the table this field points to; null otherwise. Whether create\_record / update\_record can set this column. False for a lookup, whose value is read from the linked record, and for a link to another table. # create_data_table_field Source: https://docs.qobra.co/mcp_documentation/tools/create_data_table_field Add one column to a data table and get the table back as it now stands — with its full field schema, exactly as get_data_table returns it. Add one column to a data table, and return the table as it now stands. The column is created empty on every existing record: this sets no values and no default. It is added to the table's views, to the scopes reading that table and to the plans' simulation pipelines, so it is available to a compensation and to a report straight away — load its values by Excel import from the Qobra web app, or through Qobra's public API. The table comes back with its full field schema, exactly as [`get_data_table`](/mcp_documentation/tools/get_data_table) returns it, so adding several columns in a row needs no read between them. A column synced from an integration, and a link to another data table, are set up on the table's page in the Qobra web app instead, not here. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Id of the data table to add the column to — the id list\_data\_tables returns, or the one create\_data\_table just returned. Never a table name. Display name of the column, as people will read it in Qobra — 'Amount', 'Closing date', 'Owner', 'Stage'. What the column holds. `amount` is monetary and `percentage` a ratio, where 0.15 reads as 15%; `float` is a plain number, for a count or a quantity. `user` holds a Qobra user, matched on their email when records are imported. `picklist` is one choice out of a fixed list and `multipicklist` several — both need `options`. A link to another data table is not created here. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`. The column's stable key: the identifier the public API and an Excel import address it by, distinct from the display name. Lowercase and underscore-separated, and unique within the table. Omit to derive it from the name ('Closing date' gives 'closing\_date'). The labels people choose from, for a picklist or a multipicklist column — required there, and refused on every other type. These exact labels are what a record's value is matched on when rows are imported. For an amount column, the key of the column each row's currency code is read from — a sibling field's key on an imported table, a payload key on one fed through Qobra's public API. Not a currency code itself, and refused on any other type. Omit it to denominate every row in the company currency. Optional free-text note about what the column holds. ## Response The data table the column was added to, with its full field schema as get\_data\_table returns it — the new column included. Read it to see the table as it now stands. See [`get_data_table`](/mcp_documentation/tools/get_data_table) for the field breakdown. The column just created — which of `data_table.fields` is the new one, so its id is at hand without matching on a name. # create_group Source: https://docs.qobra.co/mcp_documentation/tools/create_group Create a group — one value of an existing dimension — added straight away to the dimension's picklist in every reporting table. Create a group — one value of an existing dimension ('EMEA' under 'Region'). The dimension has to exist first; create it with [`create_group_dimension`](/mcp_documentation/tools/create_group_dimension). The group is created empty: this tool attributes no users to it. The new value is added to the dimension's picklist in every reporting table, so reports can break down by it straight away. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Id of the dimension the group belongs to — the id list\_group\_dimensions returns, or the one create\_group\_dimension just returned. Never a dimension name. Display name of the group, one value of its dimension — 'France' under a 'Country' dimension. The group's stable key: the identifier an integration matches it on, distinct from the display name. Lowercase and underscore-separated by convention (e.g. 'emea\_north'). ## Response The group as it now exists — the same row list\_groups returns, so the id can be reused straight away. Deep link to open this group in the Qobra web app. The group's stable key — the identifier used to match it when synced from an integration, distinct from its display name. Number of users currently in the group. Zero for a just-created group. # create_group_dimension Source: https://docs.qobra.co/mcp_documentation/tools/create_group_dimension Create a group dimension — the grouping axis your company's groups hang off — added straight away as a picklist field on every reporting table. Create a group dimension — a grouping axis your company's groups hang off. A dimension holds no members itself: create the axis here, then call [`create_group`](/mcp_documentation/tools/create_group) once per value it takes ('Country' → 'France', 'Spain'). The new dimension is added as a picklist field to every reporting table, so it can be used as a report breakdown straight away. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Display name of the grouping axis, as people will read it in Qobra — 'Country', 'Segment', 'Squad'. The dimension's stable key: the identifier an integration matches it on, distinct from the display name. Lowercase and underscore-separated by convention (e.g. 'sales\_region'). Optional free-text note about what the axis groups. ## Response The dimension as it now exists — the same shape list\_group\_dimensions returns, so the id can be reused straight away. The dimension's stable key — the identifier used to match it when synced from an integration, distinct from its display name. Number of groups that belong to this dimension. Zero for a just-created dimension. # create_plan Source: https://docs.qobra.co/mcp_documentation/tools/create_plan Create a compensation plan — the container its compensations hang off. The plan is created empty; add compensations with create_compensation. Create a compensation plan — the container its compensations hang off. The plan is created empty: no compensation, nobody attributed to it. Add compensations with [`create_compensation`](/mcp_documentation/tools/create_compensation), then open the plan with [`get_plan`](/mcp_documentation/tools/get_plan) or find it again in [`list_plans`](/mcp_documentation/tools/list_plans). Available only where the Qobra write tools beta is enabled. Every write to the live environment is recorded in the audit trail as the `Qobra MCP` agent; writes to a sandbox are not audited. ## Parameters Display name of the plan, as people will read it in Qobra — 'Enterprise AE 2026', 'SDR H1'. Id of the section to file the plan under — the id a plan's section carries in list\_plans. Omit it to leave the plan unfiled. Never a section name. Id of the sandbox to create the plan in — the id list\_sandboxes returns. Omit it to create the plan in the live environment. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. ## Response The plan as it now exists — the same row list\_plans returns, so the id can be reused straight away. Deep link to open this plan in the Qobra web app. The section the plan is filed under; null when unfiled. Zero for a just-created plan. # create_quota Source: https://docs.qobra.co/mcp_documentation/tools/create_quota Create a quota — the definition of a target users are measured against. Starts empty; set its target values with update_quota_values. Create a quota — the definition of a target users are measured against. This call creates the quota's *definition* and its default view, and sets no target values: the quota starts empty and every user reads as unset. Set them with [`update_quota_values`](/mcp_documentation/tools/update_quota_values), passing the id this call returns. The quota's frequency, type and currency type can only be changed afterwards on its page in the Qobra web app. Quotas synchronized from an integration are not created here either — set those up in the Qobra web app. Available only where the Qobra write tools beta is enabled. Every write to the live environment is recorded in the audit trail as the `Qobra MCP` agent; writes to a sandbox are not audited. ## Parameters Display name of the quota, as people will read it in Qobra — 'New Business ARR', 'Number of demos booked'. The period one target covers: monthly, quarterly, semesterly or annually. It fixes the granularity every target value is written at (2026-03, 2026-Q1, 2026-S1, 2026), so it is the one field to get right before any value is loaded. One of: `monthly`, `quarterly`, `semesterly`, `annually`. Unit of the target value: amount (monetary, carries a currency), percentage, or float (a plain number — use it for counts, such as a number of deals). One of: `amount`, `percentage`, `float`. Default currency for an amount quota: `company` (the company currency), `user` (each user's payment currency) or `calculation` (the user's calculation currency). Required even for a percentage or float quota, where it is not meaningful — pass `company` there. One of: `company`, `user`, `calculation`. Optional free-text note about what the quota targets. Id of the sandbox to create the quota in — the id list\_sandboxes returns. Omit it to create the quota in the live environment. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. ## Response The quota as it now exists — the same row list\_quotas returns, so the id can be reused straight away. Deep link to open this quota in the Qobra web app. One of: `amount`, `percentage`, `float`. One of: `company`, `user`, `calculation`. The quota's default view, minted at creation time. # create_record Source: https://docs.qobra.co/mcp_documentation/tools/create_record Add one record to a data table — a deal, an account, any row. One record per call, meant for adjustments. Add one record to a data table — a deal, an account, any row. Call get\_data\_table first: its `fields` give the ids to write, the type each value must take, the labels a picklist accepts, and `editable`, false for the fields no write can set (lookups and links to another table). Its `adjustment_reasons` say whether a `reason` is required here. One record per call, meant for adjustments: loading many records is an import, done from the Qobra web app. Statements are not recalculated by this call, just as when a record is added in the web app — they pick it up at the next automatic recalculation, or when someone refreshes them. Requires write permission on data tables. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Id of the data table to add the record to — the id list\_data\_tables returns. Local and integration tables are both accepted. The record's cells, one per field to set. A field left out takes its default; the name field defaults to 'New record'. Id of the column — a field id get\_data\_table returns. Only a field whose `editable` is true can be written. The cell's new value, in the shape the read tools serve it: a number for amount / float / percentage (a percentage is the ratio, 0.8 for 80%), true/false for bool, an ISO date for date, text for string, one option label for picklist, a list of option labels for multipicklist, and a user id or email for user. null empties the cell. Currency code of an amount (EUR, USD…). Omit it to keep the cell's currency; ignored on every other type. Why the record is added — one of the table's `adjustment_reasons` from get\_data\_table. Required only when that list is not null; omit it otherwise. Optional note kept in the record's history. The call is refused, with a message naming the problem, when a `field_id` is unknown or listed twice, when a field is a lookup or a link to another table, when a value does not match its field's type or picklist options, or when the table requires a `reason` and none from its `adjustment_reasons` was passed. ## Example ```json theme={null} { "data_table_id": "665f1c2e8a4b3d0012a7e9f1", "values": [ { "field_id": "665f1c2e8a4b3d0012a7e9f2", "value": "Acme renewal" }, { "field_id": "665f1c2e8a4b3d0012a7e9f3", "value": 12000, "currency": "EUR" }, { "field_id": "665f1c2e8a4b3d0012a7e9f4", "value": "2026-09-15" }, { "field_id": "665f1c2e8a4b3d0012a7e9f5", "value": "Closed won" } ], "reason": "Missing deal", "comment": "Signed offline, not yet in the CRM." } ``` ## Response The table the record was created in, whose `fields` header names the record's values in order — the same row list\_data\_tables returns. See [`list_data_tables`](/mcp_documentation/tools/list_data_tables) for the full field breakdown. The record as it now exists — the same row list\_data\_table\_records returns, so the id can be reused straight away. Its `values` are read in the order of `data_table.fields`. See [`list_data_table_records`](/mcp_documentation/tools/list_data_table_records) for the full field breakdown. # create_report Source: https://docs.qobra.co/mcp_documentation/tools/create_report Create an empty report — no metrics, fields or chart attached yet. Attach the rest from the Qobra web app or by prompting the Analyst. Create an empty report — no metrics, fields or chart attached yet. The report comes back with its id and a link to it; reuse that id rather than looking it up again, which can miss a creation this recent. Attaching metrics or data table fields, configuring a pivot table or a chart, is not something this tool does — prompt the Analyst to fill in the report it just created, or do it from the Qobra web app. A record report tracks the rows of one data table, named through `tracked_data_table_id`; a statement report tracks payout statements and takes no data table. Available only where the Qobra write tools beta is enabled. Every write to the live environment is recorded in the audit trail as the `Qobra MCP` agent; writes to a sandbox are not audited. ## Parameters Display name of the report, as people will read it in Qobra — 'Q3 pipeline', 'New Business deals'. What the report is built on: `statement` (payout statements) or `record` (the rows of a data table, named by `tracked_data_table_id`, which is then required). One of: `statement`, `record`. Optional free-text note about what the report is for. Id of the data table this report tracks — the id list\_data\_tables returns. Required when `report_type` is `record`, and refused otherwise. Id of the section to file the report under — the id a report's section carries in list\_reports. Omit it to leave the report unfiled. Never a section name. Id of the sandbox to create the report in — the id list\_sandboxes returns. Omit it to create the report in the live environment. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. ## Response The report as it now exists — the same row list\_reports returns, so the id can be reused straight away. See [`list_reports`](/mcp_documentation/tools/list_reports) for the full field breakdown. # create_request Source: https://docs.qobra.co/mcp_documentation/tools/create_request Open a request about one or more statements or deals, with its first message, members and an assignee, in one call. Open a request — the collaboration thread people settle a commission question in. Optionally about statements: each one in `statements` opens attached to the request, exactly as from the statement page in the web app, and a record narrows it to one deal of that statement. One request can cover several statements — open one per question, not one per statement. A statement is linked once, about one of its records at most. The caller is the request's author, and `message` is posted in their name. Only a request's members are notified of its messages, and most users see only the requests they belong to — so list in `member_ids` everyone who should follow it: the user whose statement it is about, anyone `message` @-mentions. A mention alone neither adds nor notifies them. Close, reassign or re-file the request later with [`update_request`](/mcp_documentation/tools/update_request). Attachments are not supported here. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Title of the request, as it reads on the requests board — 'January commission looks low'. Keep it short; the detail goes in `message`. First message to post in the thread, written as the caller. Plain text, as in the web app: anything between `<` and `>` is removed, so write no HTML and spell comparisons out in words. Omit to open the request with an empty thread. The statements the request is about — one user in one month each, optionally narrowed to one of its records. Omit for a request about no statement in particular. Id or exact email of the user whose statement it is. Month of the statement, as YYYY-MM (e.g. '2026-03'). The one record of this statement the request is about — a deal, most often. Omit to link the statement as a whole. Id of the scope the record was frozen into — the `id` of one of get\_statement\_compensation's scopes. Id of the record — the `record_id` list\_statement\_records returns for that scope. Id of the topic to file the request under — one of `requests.topics` from get\_company\_settings. Omit to file it under no topic. The call is refused when the company defines no topics, or when the id names none of them; the refusal lists the available topics and their ids. Ids or emails of the people to add to the request as participants, beside its author — as list\_users returns them. Emails match case-insensitively. Each one can read and answer the thread, is notified they were added, and appears in the request's `members`. Omit to open the request with its author as the only member. Id or email of the person to assign the request to — the one expected to handle it, as list\_users returns it. They become a member and are notified it is theirs. Omit to leave it unassigned, as the web app opens a request. ## Response The request as it now exists — the same row list\_requests returns, so the id can be reused straight away. Deep link to open this request in the Qobra web app. The #-style reference shown in the UI. Admin-configured category used to classify the request; null if none. open (awaiting handling) or closed (resolved). One of: `open`, `closed`. The linked deal/record when the statement is record-based; null otherwise. The first message, when one was posted with the request; null when the request was opened with no message. The message as written, with every @-mention rendered as `@` instead of the raw user id the web app stores. See `mentions` for the ids and emails behind those names. The users @-mentioned in `content`, in order of first appearance; empty when the message mentions nobody. A mention whose id matches no user is left as a raw handle in `content` and is absent here. # create_user_attribute Source: https://docs.qobra.co/mcp_documentation/tools/create_user_attribute Create one user attribute — a column on your company's people. The attribute is opened empty on every existing user, so creating it touches the whole user table. Create one user attribute — a column on your company's people. The schema of what the company records about a person (Segment, Hiring date…), which is what scopes filter on and what a report breaks down by. The attribute is company-wide: it is not scoped to a sandbox, and it shows up in [`list_user_attributes`](/mcp_documentation/tools/list_user_attributes) last in the display order. **This writes to every existing user.** The attribute is opened on each one of them at creation — empty, except a `bool`, which starts at false for everyone — and an `amount` attribute also records the company currency there. So creating an attribute touches the whole user table, not just the definition. Load the values by Excel import from the Qobra web app, which is also where a default value is set. Creating the *people* is not done here: they come in by Excel import from the Qobra web app, which is also where an attribute synced from an HRIS or a CRM is set up. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Display name of the attribute, as people will read it in Qobra — 'Segment', 'Hiring date', 'Region'. The attribute's stable key: the identifier the public API and an Excel import address it by, distinct from the display name. Lowercase and underscore-separated, and unique across the company's attributes ('Hiring date' gives 'hiring\_date'). The public API already serves every user an `id`, `email`, `status`, `name`, `role`, `currency` and `calculation_currency`, so those seven are taken. What the attribute holds. `amount` is monetary and `percentage` a ratio, where 0.15 reads as 15%; `float` is a plain number, for a count or a quantity. `user` holds a Qobra user, matched on their email when people are imported. `picklist` is one choice out of a fixed list and `multipicklist` several — both need `options`. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`. The labels people choose from, for a picklist or a multipicklist attribute — required there, and refused on every other type. These exact labels are what a user's value is matched on when people are imported. Include this attribute in the company's user exports. Off by default, as it is in Qobra. Refuse two users the same value — for an employee number or a payroll id, not for a segment everyone shares. Applies to a `string` or a `float` attribute only. ## Response The attribute as it now exists — the same row list\_user\_attributes returns, so the id can be reused straight away. What the company says this attribute means, in their own words. Null when they wrote none. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`. Selectable option labels for a picklist or multipicklist attribute; null for every other type. True when the value cannot be edited in Qobra — it is owned by the source system that syncs it. The company connection this attribute is synced from; null for a just-created attribute maintained in Qobra itself. # get_audit_log Source: https://docs.qobra.co/mcp_documentation/tools/get_audit_log Full detail of one audit-trail entry, including its change payload. A list\_audit\_logs row plus `changes` — the action's field-level before/after diff as a clean list — and its `description`. Reach for it after list\_audit\_logs surfaces an entry whose change detail you need to explain. For the affected resource's own current state, use that resource's tool. ## Parameters Id of the audit-trail entry to inspect — the id returned by list\_audit\_logs. ## Response Deep link that opens this audit-log entry's detail modal in the Qobra web app. When the action happened (UTC, ISO-8601). Coded name of the audited event, e.g. 'UserUpdated' or 'CompensationRuleUpdated' — the precise action taken. Id of the affected resource; null when not applicable. Human-readable name of the affected resource, if known. ObjectId of the actor; feed it to get\_user for detail. Actor's email as recorded at action time — the identifier to recognize the user by. Null for a system/script action. Actor's role name as recorded at action time. The surface the action was performed through: `Qobra UI` is the web app, its in-app AI agents (the Analyst, the Architect) included; `Qobra MCP` is an MCP client acting as the actor; `Qobra Onboarding` is the self-serve onboarding agent. Null on entries written before the surface was recorded: unknown, not the web app. One of: `Qobra UI`, `Qobra MCP`, `Qobra Onboarding`. The action's field-level changes as a clean before/after list. Empty when the action carries no field diff (e.g. a create/delete, or a payload-less event). Name of the field that changed. Value before the change; null when unset or on a create. Value after the change; null when unset or on a delete. # get_commission_letters_campaign Source: https://docs.qobra.co/mcp_documentation/tools/get_commission_letters_campaign Return one commission-letter campaign's meta: overall status, envelope counts, how pre/post signers are chosen, and CC slots. Return one commission-letter campaign's detail, without its per-recipient letters. The natural follow-up to `list_commission_letters_campaigns`: take a campaign row and read its meta — template, subject, send choice, when and by whom it was sent, a derived `overall_status`, `envelope_counts`, how the pre/post signers are chosen and the campaign's CC slots. Every person is surfaced by email, never a bare id. Each recipient's letter (envelope status, signers, people in CC) is read page by page with `list_commission_letters`. A campaign you can't access is reported as not found. The letters' PDF content is omitted. ## Parameters Id of the campaign to read — the id list\_commission\_letters\_campaigns returns. ## Response Deep link to open this campaign in the Qobra web app. How the letters are delivered: email (plain email) or esignature (electronic signature). One of: `email`, `esignature`. One of: `draft`, `generating`, `sent`, `awaiting_signatures`, `completed`, `action_needed`. Letters tallied per e-signature envelope status; empty for e-mail campaigns (they have no envelopes). How the pre-signer is chosen: specific (a fixed named person) or dynamic (resolved from the org chart, 1-3 levels above the letter owner). One of: `specific`, `dynamic`. Same as presigner\_mode, for the post-signer. One of: `specific`, `dynamic`. Campaign-level CC slots. Each is either a specific employee or a manager N levels above the letter owner. How this CC is chosen: specific (a fixed named person) or dynamic (resolved from the org chart, 1-3 levels above the letter owner). One of: `specific`, `dynamic`. One of: `directly_above__user`, `two_levels_above__user`, `three_levels_above__user`. # get_company_settings Source: https://docs.qobra.co/mcp_documentation/tools/get_company_settings Return your company's parameter sheet — statement periods, currencies, AI agents, enabled modules — one block per settings section. Return how your company is configured, one block per settings section. Reach for this when the question is about the *setup* rather than the figures — "are draws activated?", "who can use the Sales Coach?", "since when do statements exist?" — and when a tool comes back empty and you need to know whether the feature is simply off. [`get_identity`](/mcp_documentation/tools/get_identity) already carries the fiscal calendar, the one piece that conditions every period label; the `profile` block repeats it so the sheet reads on its own. The currency and the timezone here are the *company's* — the ones on identity's user are what you personally are paid in and work in. Each block carries the settings page it is edited on, so an answer about a setting can point at where to change it. A null `settings_url` means that section is not self-serve and Qobra support changes it. **An optional module that is unusable is absent, not a block of falsy fields.** `requests`, `validation_workflow`, `draws` and `licences` are null in that case, and `unavailable_modules` says why for each: the subscription tier does not include it (the user has to talk to Qobra about upgrading — read `package` for the tier they are on), it is included but switched off, or you personally may not read it. Each row's `remedy` is the sentence to tell the user. None of them is a reason to retry. Takes no arguments: the company is the one your session is scoped to. For exchange rates between the available currencies, and for what a *user* is paid in, see the currency fields on the user and statement tools. ## Response The company's Qobra subscription tier: core, advanced, scale, or others. What a tier includes is why a module below may be absent — see `unavailable_modules`. One of: `core`, `advanced`, `scale`, `others`. Always present. IANA timezone the company's dates are expressed in. Same shape as on `get_identity`: the fiscal year in progress, as a plain month range. Repeated here so the sheet reads on its own. The fiscal year label the current calendar day falls in, formatted like `FY2027`. First month of the fiscal year, `YYYY-MM`. Last month of the fiscal year, `YYYY-MM`. Deep link to the company settings page. Always present. `YYYY-MM`; the earliest period the company's statement navigation offers. A display floor, not an existence boundary — statements before it can still exist, and `list_statements` will return them if you ask for that period. Null means no floor was configured. How many periods beyond the current one are opened ahead of time. Hour of the day (0-23, UTC) statements are recalculated automatically — when figures refresh without anyone acting. Always null: these are set by Qobra, not on a settings page. Ask Qobra support to change them. Always present. The currency the company consolidates totals in. Whether commissions may be computed in a currency other than the one they are paid in — the multi-currency feature AND the company's own switch. Every currency the company has exchange rates for. Empty means it operates in a single currency. Deep link to the currencies settings page. Always present. One row per AI agent Qobra ships. Which AI agent this row is about: `sales_coach` (explains a rep their statement), `analyst` (answers reporting questions), `architect` (builds compensation plans), `mcp` (the Qobra MCP server). One of: `sales_coach`, `analyst`, `architect`, `mcp`. Deep link to this agent's settings page. Whether the agent is usable in this company — the subscription feature AND the company's AI switches, not one of the two. False is the answer to "why can nobody use it?". Whether *you*, the calling user, may use the agent — `available` narrowed by the pilot group when the agent is in pilot mode. Rollout mode: `all` (everyone in the company) or `pilot` (only the users listed in `pilot_users`). One of: `all`, `pilot`. The users allowed to use the agent while it is in pilot mode. Empty when mode is `all` — the whole company is then allowed — and empty as well when you may not read the company's AI settings, which never affects your own `available_to_you`. Deep link to the AI settings page. Null when requests are unusable — see `unavailable_modules`. The topics a request can be filed under. Empty when the company defined none. Deep link to the requests settings page. Null when the approval workflow is unusable — see `unavailable_modules`. Deep link to the validation-workflow settings page. Null when draws are unusable — see `unavailable_modules`. Always null: draws are switched on by Qobra, not on a settings page. Ask Qobra support to modify this. Null when seats are unusable, or when you may not read the company's subscription — see `unavailable_modules`. Whether seats are enforced. When true, only a licensed user's commissions are computed. How many seats the company has been granted. Deep link to the subscription settings page. One row per null block above, saying why it is null and what would bring it back. Empty when every module is available to you. Which block is absent: `requests` (collaboration threads), `draws` (recoverable advances), `validation_workflow` (statement approval), `licences` (seats). One of: `requests`, `draws`, `validation_workflow`, `licences`. `not_in_package` (the subscription tier does not include it), `switched_off` (included but nobody turned it on), `not_yours_to_read` (it may well be on — you lack the permission). One of: `not_in_package`, `switched_off`, `not_yours_to_read`. What to tell the user, in a sentence. Says whether this is a package upgrade, a settings change, or a word to Qobra support — never retry the tool. # get_dashboard Source: https://docs.qobra.co/mcp_documentation/tools/get_dashboard Read one dashboard's full detail: its chart and pivot tiles, their row-by-row layout, and the configuration of every report the tiles render. Return one dashboard's full detail: its tile layout and the reports it shows. The natural follow-up to `list_dashboards` when you need to understand an existing dashboard — not just which reports it groups, but how they are laid out and what each report is built on. On top of the `list_dashboards` row it adds `visualizations` (the flat list of chart / pivot tiles), `layout` (their arrangement, row by row) and `reports` (the full detail of every distinct report the tiles render — the data table each is built on, its type, who can view it, and its available visualization types). You only see the dashboards you have access to — every one of the company's at full dashboard access, otherwise only those shared with you (by role or individually). A dashboard you can't reach is reported as not found. `reports` describes how each tile is built, never its data: the computed rows stay behind `get_report_data`, which you reach only for a report granted to you directly — a dashboard grant alone gets you the tiles' chart / pivot values (`get_report_chart_data` / `get_report_pivot_data`), not the granular rows behind them. ## Parameters Id of the dashboard to read — the id list\_dashboards returns. ## Response Deep link to open this dashboard in the Qobra web app. The chart / pivot-table tiles the dashboard shows, each referencing the report it is built on. Stable id of this tile. The layout rows reference tiles by this id, so it is how you place a tile in the dashboard's arrangement. Id of the report this tile renders. Null for a tile not backed by a report. The tile's display name (usually the report's name). One of: `chart`, `pivot_table`. The dashboard's tile arrangement, top to bottom — each row lists the tiles placed on it, left to right. This is how the visualizations are laid out on the page. The visualization\_ids sitting side by side on this row, in left-to-right order. Cross-reference each id with the matching entry in the dashboard's 'visualizations' to know which report tile it is. Full detail of every distinct report the dashboard's tiles are built on: the tracked data table for record reports, its type, who can view it, and every tile type it can render — shown on this dashboard or not. Deduplicated — a report shown as both a chart and a pivot table appears once, and the 'visualizations' list above is where each tile's own type is named. Deep link to open this report in the Qobra web app. What the report is built on: statement (payout statements) or record (the rows of the tracked\_datatable). One of: `statement`, `record`. Source data table tracked by a record report, with its id and name; null for statement reports. The dashboard tiles this report can render: `chart` when a chart is configured on it, `pivot_table` when a pivot table is. Empty when neither is — the report then has no tile to show on a dashboard. The section the report is filed under (id and name), or null when unfiled. Who can view the report, split into roles and users. Each entry carries its id, name, and access level. Only returned to callers with write access on reports; null for read-only callers. One of: `read`, `write`, `delete`. One of: `read`, `write`, `delete`. One of: `read`, `write`, `delete`. # get_data_table Source: https://docs.qobra.co/mcp_documentation/tools/get_data_table Get one data table's field schema by id, including picklist option labels, source, public-API key, and any synced integration. Get one data table's full field schema, resolved by name or id. Returns the same shape as a `list_data_tables` row — source, public-API key, integration and fields — plus, for every PICKLIST / MULTIPICKLIST field, its selectable option labels. Reach for it to learn a table's columns and the exact option labels to filter its records on; use `list_data_tables` to discover which tables exist. You only see the data tables you have access to. An id that names no accessible table is an error — a table outside your scope reads exactly like one that does not exist. ## Parameters Id of the data table to fetch — the id list\_data\_tables returns. ## Response Deep link to open this data table in the Qobra web app. One of: `kombo`, `salesforce`, `pipedrive`, `microsoft_dynamics`, `hubspot`, `odoo`, `zoho`, `attio`, `postgresql`, `gcp-postgresql`, `snowflake`, `snowflake-keys`, `redshift`, `microsoft-azure-sql`, `microsoft-azure-synapse`, `bigquery`, `s3`, `sftp`, `netsuite`, `public_api`. The views the caller may query for this table's records — pass one's id as `view_id` to list\_data\_table\_records to narrow the rows. Empty when no view is relevant. Stable technical key for this field in Qobra's public API (use it as a durable reference; the display name can change). Field data type: amount, percentage, float, string, bool, date, picklist, multipicklist, user, or object (a reference to another data table — see linked\_to). One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. When type is object, the table this field points to; null otherwise. Name of the linked table. `null` when the caller has no access to the linked table (for example, when the link is visible only through a shared view on the current table). `id` is always returned. Whether create\_record / update\_record can set this column. False for a lookup, whose value is read from the linked record, and for a link to another table. Selectable option labels for a PICKLIST / MULTIPICKLIST field, in definition order; null for every other field type. Use these exact labels when filtering records on this column. The reasons a manual edit of this table's records must cite, passed as `reason` to create\_record / update\_record; null when an edit here needs none. # get_group Source: https://docs.qobra.co/mcp_documentation/tools/get_group Return a single group with its full member list: name, key, dimension, and every past and present user attribution with its membership window. Return a single group with its full member list. Beyond the group's identity (name, key, dimension), the result joins in every user attribution — past and present — as an `{id, email, name}` user reference plus a `YYYY-MM` membership window; a null departure means the membership is still ongoing. ## Parameters Id of the group to look up — the id list\_groups returns. ## Response Deep link to open this group in the Qobra web app. The group's stable key — the identifier used to match it when synced from an integration, distinct from its display name. The dimension the group belongs to (the kind of grouping, e.g. 'Country' or 'Team'). Null if the dimension is unknown. Number of users currently in the group (attributions active as of today). Past and future members are excluded. Every user attribution on the group, past and present, each with its membership window. A user with several stints appears once per attribution. YYYY-MM; the month the user joined the group. YYYY-MM; null while the membership is still ongoing. # get_help_center_article Source: https://docs.qobra.co/mcp_documentation/tools/get_help_center_article Return one Help Center article's full markdown body by id, including its title, author description, and source URL, ready to quote or summarize. Return one Help Center article's full markdown body, by id. The natural follow-up to `list_help_center_articles`: take a row's `id` and read the whole article — its title, author description, full body (markdown) and source URL. An article in a collection the caller isn't entitled to reads as not found, exactly like an unknown id. ## Parameters Id of the article to read — the id list\_help\_center\_articles returns. ## Response The author-written description. Public URL of this Help Center article (an external help.qobra.co link, not a Qobra web-app page); null if unpublished. # get_identity Source: https://docs.qobra.co/mcp_documentation/tools/get_identity Return your own identity, company and access scope: who you act as, your language and timezone, and which resources you may query. Return your own identity, company and access scope. Use this first to learn *who you are acting as*, *which company you are in*, and *what you are allowed to query*. The caller is returned as the same compact summary `list_users` gives (name, email, role, currencies, status), plus your own `language` and `timezone` — preferences that say how to answer *you* and appear here and nowhere else. The company is deliberately minimal: id, name and the fiscal calendar. Pass your own `role` id to `get_role` to read the permissions behind your limits. Read the fiscal calendar before interpreting any period. Every `YYYY`, `YYYY-SX` and `YYYY-QX` label another tool returns is numbered on it, and the company's own fiscal year need not start in January. A response of `FY2027, 2026-02 → 2027-01` says the current fiscal year is *FY2027* and spans February 2026 through January 2027. For the rest of the configuration — statement periods, currencies, the AI agents your company runs, which optional modules are switched on — call [`get_company_settings`](/mcp_documentation/tools/get_company_settings). For the connected integrations, call [`list_integrations`](/mcp_documentation/tools/list_integrations). `access` carries one key **per resource you can actually reach** (`statement`, `request`, `quota`, `data_table`, `report`, `dashboard`): how broadly you see it (`scope` plus a plain-language `description`) and, for statements and requests, the saved `views` you may pass to the matching list tool. Each view also carries `access` — the roles and users who can read it — when you are allowed to share that resource. A resource you have no access to carries **no key at all** — if a key is missing, do not try to query that resource. Reports and dashboards report `all` or `shared` only, like quotas: each *is* its own view, so a share names the resource itself. A caller who reaches a report **only** through a shared dashboard carries no `report` key — the values its tiles display are named in that dashboard's `description` instead of inventing a fifth `scope`. `shared_resources` names the resources shared with you *individually* — the grants that reach past your role, already counted in `access` above. Pass your own `role` id to [`get_role`](/mcp_documentation/tools/get_role) for the other channel: what is shared with everyone holding your role. The rows follow the same shape `get_role` returns. Sharing is a live-environment concept, so those are the live ones. Takes no arguments: the identity is read from the authenticated session, not passed in. For the *full* profile (managers, groups, plan attributions, custom attributes), call `get_user` with your own id or email. ## Response Same compact fields as each item in [`list_users`](/mcp_documentation/tools/list_users)'s response (`id`, `resource_url`, `name`, `email`, `status`, `role`, `manager`, `payment_currency`, `calculation_currency`, `licensed`, `license_missing`, `integrations`, `last_synced_at`), plus two fields unique to this tool: The language you set in Qobra — the one to answer you in when nothing else says otherwise. IANA timezone you read dates in. The company's fiscal year in progress, as a plain month range. The numbering basis of every period label another tool returns — a `YYYY-Q1` or `YYYY-S1` is counted on it, and a fiscal year need not start in January. The fiscal year label the current calendar day falls in, formatted like `FY2027`. First month of the fiscal year, `YYYY-MM`. Last month of the fiscal year, `YYYY-MM`. One key per resource you can actually reach (`statement`, `request`, `quota`, `data_table`, `report`, `dashboard`): how broadly you see it (`scope` plus a plain-language `description`) and, for statements and requests, the saved `views` you may pass to the matching list tool. A resource you have no access to carries no key at all. Full per-resource schema: [Access object reference](/mcp_documentation/reference/access_object). Resources shared with you *individually*, on top of whatever your role grants — call [`get_role`](/mcp_documentation/tools/get_role) with your own `role` id for the other channel, which covers everyone holding it. This is what lets you reach a view your role alone would deny, so `access` above already accounts for it. Empty when nothing is shared with you individually. Full field reference (`resource`, `resource_type`, `parent_name`, `access`): [`get_role`](/mcp_documentation/tools/get_role)'s `shared_resources` — same shape. Live environment only. # get_import Source: https://docs.qobra.co/mcp_documentation/tools/get_import Return the full detail of one data import: per-batch status, progress, record counters, error detail and category, rejected rows and a deep link to the run in the Qobra web app. Return the full detail of one data import, including its per-batch breakdown. A list\_imports row plus `configs` — one entry per resource batch, each with its own status, progress, record counters (created / updated / deleted / skipped), error state, the connection it synced from and the rows it rejected or skipped (the first 20, each with its reason). Reach for it after list\_imports surfaces a run whose outcome you need to explain, e.g. why an integration refresh failed or why some users did not come in. You only see imports that touched a resource type you may read; an import whose every resource type is outside your permissions is reported as not found. ## Parameters Id of the import to inspect — the id returned by list\_imports. ## Response Deep link that opens this import expanded in the Qobra web app, on the history page of a resource type it touched. Null only for an import with no batch yet, which no history page lists. Overall status: 'scheduled', 'started' or 'finished'. One of: `scheduled`, `started`, `finished`. What kicked the import off, e.g. 'scheduled\_refresh', 'integration\_refresh' or 'quota\_sync'. Null when unknown. One of: `scheduled_refresh`, `integration_refresh`, `integrations_with_frequency_refresh`, `braqoffice_refresh`, `users_creation`, `user_integration_sync`, `user_file_import_refresh`, `user_attributes_creation`, `groups_creation`, `data_table_refresh`, `data_table_settings_update`, `field_creation`, `quota_creation`, `quota_sync`, `quota_refresh`, `quota_settings_update`. Overall completion 0–100, averaged across the import's batches (100 when it has no batch). True when a batch you can read ended in error; get\_import says which and why. True when a batch you can read finished but rejected or skipped some rows; get\_import lists them. A run can carry a warning without an error. Distinct resource types the import touched — any of 'data\_table', 'quota', 'user'. The user who triggered the import; null for a system or scheduled import. Per-batch breakdown of the import — one entry per resource batch, each with its own status, progress, record counters and error state. Only the batches of resource types you can read are listed. How the data reached Qobra for this batch: 'integration' (a CRM sync), 'api', 'csv' (a file upload) or 'bulk\_action'. One of: `integration`, `api`, `csv`, `references`, `provisioning`, `bulk_action`. What this batch imported into: 'data\_table', 'quota' or 'user'. One of: `data_table`, `quota`, `user`. ObjectId of the imported resource (the data table or quota); null for a company-wide user import. The connection this batch synced from — one run can bundle several. Null for a CSV upload, a bulk action, an API push tied to no connection, or a connection since removed. One of: `kombo`, `salesforce`, `pipedrive`, `microsoft_dynamics`, `hubspot`, `odoo`, `zoho`, `attio`, `postgresql`, `gcp-postgresql`, `snowflake`, `snowflake-keys`, `redshift`, `microsoft-azure-sql`, `microsoft-azure-synapse`, `bigquery`, `s3`, `sftp`, `netsuite`, `public_api`. Batch status: 'scheduled', 'started' or 'finished'. One of: `scheduled`, `started`, `finished`. Completion of this batch, from 0 to 100. True when this batch ended in error. Why the connection failed, when the error came from the integration: 'authentication' (credentials rejected, reconnect it), 'permission' (the connected account lacks access), 'configuration' (a setting, query or object the customer set up is wrong or missing), 'rate\_limit' (too many requests, retry later), 'unavailable' (the integration is down or answered unreadably), 'invalid\_request' (the integration rejected the request itself) or 'unknown'. Null for an error raised inside Qobra, or one recorded before categories existed. One of: `authentication`, `permission`, `configuration`, `rate_limit`, `unavailable`, `invalid_request`, `unknown`. True when this batch finished but rejected or skipped some rows, listed in rejected\_rows. A batch can carry a warning without an error. What the batch did within its resource\_type. A user import names the part of the users it imported: 'users-attributes' (the users and their attributes), 'hierarchy' (who manages whom) or 'groups' (group memberships). A bulk action names the action it applied — on users: assign\_manager, assign\_licence, change\_role, change\_currency, change\_calculation\_currency, set\_attribute, assign\_group, revoke\_access, send\_invitation; on data-table records: edit\_records, reset\_records, activate\_records, deactivate\_records, delete\_records, change\_name\_field. Null for any other batch. How many rejections the import kept for this batch: one per rejected row, except a record\_range, which stands for a whole chunk of records. It keeps the last 100 only, so when rejected\_rows\_truncated is true there were more. For the number of records a batch did not apply, read number\_of\_records\_skipped. True when the batch reached the 100 rejected lines the import keeps: it rejected at least rejected\_row\_count rows, possibly many more. The first 20 rows the import kept, in the order it recorded them, each with its reason. When rejected\_rows\_truncated is true, the import had already dropped older rows, so these are not the batch's first rejections. The row's own source values are never included; the import's resource\_url opens the list the web app shows. What was rejected, by kind. 'line': a line of a CSV file or a record of an API payload, by position. 'user': a user batch's attribute error, or a user a bulk action failed on. 'group': a group a bulk action failed on. 'record': a record a data-table bulk action failed on. 'record\_range': a whole chunk of records a data-table bulk action failed on at once. Null when the import recorded nothing to locate the row by (a quota row, whose source values are never served). Always `line`. Line of the CSV file, or position of the record in an API payload, counted from 1. Always `user`. Id of the user the rejection is about, always set. That user, or null when no user has that id any more — typically a bulk action reporting it not found. Key of the user field an attribute error is on. Null when the rejection is about the user as a whole. Always `group`. The group a bulk action failed on. Always `record`. Id of the record a data-table bulk action failed on, in the table the batch's resource\_id names. Always `record_range`. Id of the first record of the chunk that failed. Id of the last record of that chunk: the failure covers every selected record between the two, in the table the batch's resource\_id names. Why the row was rejected or skipped, as the import recorded it, cut at 500 characters (ending with '…' when cut). Null when it recorded none. Total records processed by this batch, when tracked. # get_plan Source: https://docs.qobra.co/mcp_documentation/tools/get_plan Get the design view of one compensation plan: its compensations with periodicity, date range and the reporting metrics each feeds, plus its members. Get the design (conception) view of one compensation plan. Returns the plan with its compensations (their periodicity, date range and the reporting metrics each one feeds) and its attributed members. This is the plan-structure counterpart to get\_statement\_compensation, which returns the *calculated* values on an existing statement. A plan you can't access reads exactly like one that doesn't exist, so the error never reveals anything outside your scope — a plan living in a sandbox you may not read included. ## Parameters Id of the plan to open — the id list\_plans returns. ## Response Deep link to open this plan in the Qobra web app. Folder/group this plan is filed under (a concept shared with reports); null if unfiled. How many compensations the plan holds. Open the plan with get\_plan for each compensation's periodicity, date range and the reporting metrics it feeds, plus the plan's members. The rest of the calculation behind a compensation is not exposed there — read a statement's computed breakdown with get\_statement\_compensation. The plan's compensations with their configuration. True when the compensation has no fixed date range (it applies every period). YYYY-MM; null when the compensation is recurring. YYYY-MM; null when the compensation is recurring. "live" when the compensation is active on the plan; "draft" while it is still being edited and not yet applied. One of: `live`, `draft`. The reporting metrics this compensation feeds, one entry per metric flagged on one of its variables. Empty when this compensation feeds none — which says nothing about the plan on its own: a plan is missing from a report built on a metric only when none of its compensations feeds that metric. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. The users and groups attributed to the plan. The attributed user; null for group attributions. The attributed group; null for user attributions. # get_report Source: https://docs.qobra.co/mcp_documentation/tools/get_report Read one report's configuration: tracked data table, columns and their types, filters, sort, aggregates, and pivot or chart layout when set. Return one report's full detail: the list summary plus its configuration. The natural follow-up to `list_reports`: take a report and read how it is built — its tracked source datatable for record reports, its columns and their types, the filters and sort applied, the bottom-of-column aggregates, and the pivot-table / chart layout when configured. This is the configuration that makes the report's data interpretable; the computed rows and pivot cells live behind `get_report_data` and `get_report_pivot_data`, while the values displayed by a configured chart live behind `get_report_chart_data`. `access` (who can view the report) is only returned to callers with write access on reports; read-only callers get `access: null`. ## Parameters Id of the report to read — the id list\_reports returns. ## Response Source data table tracked by a record report, with its id and name; null for statement reports. The dashboard tiles this report can render: `chart` when a chart is configured on it, `pivot_table` when a pivot table is. Empty when neither is — the report then has no tile to show on a dashboard. One of: `read`, `write`, `delete`. One of: `read`, `write`, `delete`. One of: `read`, `write`, `delete`. One of: `AND`, `OR`. # get_report_chart_data Source: https://docs.qobra.co/mcp_documentation/tools/get_report_chart_data Return every value a report's configured chart displays: categories and series, donut slices, or bubble points, computed in a single call. Return every value produced by a report's configured chart. The chart-focused sibling of `get_report_data` and `get_report_pivot_data`: use this after `get_report` when you need the values actually displayed by the chart — categories, series, slices or points — rather than reconstructing them from raw rows or pivot cells. Bar, line and combo charts return categories and series; donut charts return a metric and slices; bubble charts return points. Pivot-backed charts use their relevant aggregates and subtotals, while raw-backed charts apply the chart's summing and percentage-normalization rules. A date-typed category (bar/line/combo `categories`, donut `slices`, bubble `points`) is returned as a pre-formatted period label, not a raw ISO date. The label reads exactly like the front-end chart axis, on the company's fiscal calendar, and in the caller's language: `Dec 2026` for a monthly axis, `Q1 2026` for a quarter, `H1 2026` for a semester, `2026` for a year (`Janv. 2020` / `T1 2026` / `S1 2026` in French). Chart axes never show a day. Series values remain numeric. The complete chart payload is returned in one call, with no pagination. A missing chart configuration, excessive cardinality, or a report too large to calculate raises a clear error. You only see reports you have access to — those granted to you directly, plus any rendered as a chart on a dashboard shared with you. ## Parameters Id of the report to read — the id list\_reports returns. ## Response The response shape depends on the chart's type. `chart_type` is the discriminator; `data_source` reflects the underlying report configuration (`raw`, `chart` or `pivot_table`); `autosummed` is `true` when the chart summed values automatically to produce these totals. ### Bar, line and combo charts Deep link to open this report in the Qobra web app. One of: `bar`, `line`, `combo`. One of: `raw`, `chart`, `pivot_table`. The x-axis values, in the order the chart lays them out. One entry per plotted series. `values` is aligned to `categories` (same length, same order); `tooltip_values` mirrors it when the chart shows a secondary tooltip metric. One of: `bar`, `line`. The breakout category this series is split by, or `null` when the series isn't broken out. ### Donut charts Deep link to open this report in the Qobra web app. Always `donut`. One of: `raw`, `chart`, `pivot_table`. The field the donut is measuring. One entry per donut slice. ### Bubble charts Deep link to open this report in the Qobra web app. Always `scatter`. One of: `raw`, `chart`, `pivot_table`. One entry per plotted bubble. `value` sizes the bubble and is `null` when the chart doesn't use a size metric. # get_report_data Source: https://docs.qobra.co/mcp_documentation/tools/get_report_data Read a report's computed rows: displayed columns, row values, and bottom-of-column aggregates, respecting filters, sort, and visibility. Return a report's computed rows — its actual data, filtered and sorted. The follow-up to `get_report`: once you know how a report is built, read its data. Returns the displayed columns, the row values (aligned to the columns), and the bottom-of-column aggregates, respecting the report's filters, sort and the caller's visibility. Use `get_report` first to read the field ids, types and filter/sort configuration that make these rows interpretable; use `get_report_pivot_data` for a report shown as a pivot table. Date values in `rows` are pre-formatted strings, not raw ISO dates. Each date column is rendered exactly the way the report displays it on screen, on the company's fiscal calendar, and in the caller's language: the statement period column shows a month (`Dec 2026` in English, `Déc. 2026` in French), and every other date column shows a day (`Dec 23, 2026` / `23 déc. 2026`). Do not try to parse these values as dates — treat them as display labels. `bottom_aggregates` still return raw values, so a `min`/`max` over a date column is a parseable ISO date. Paginated like every list tool: `limit` rows per call, `offset` to walk the pages (`next_offset` is `null` on the last one), `total_row_count` for the full size. Only the report's displayed columns are ever returned. A report too large to compute on the fly raises a clear error asking you to narrow it with filters. You only see reports you have access to. ## Parameters Id of the report to read — the id list\_reports returns. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. Restrict the returned columns to these field ids (as returned by get\_report). Omit to return all of the report's displayed columns. Hidden or unknown field ids are rejected. ## Response One of: `number`, `boolean`, `date`, `string`. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. One entry per row, aligned to `columns`. Values for `date` columns are pre-formatted period labels (see the note above), not raw dates. One of: `empty`, `not_empty`, `distinct`, `count`, `sum`, `average`, `min`, `max`. One of: `number`, `boolean`, `date`, `string`. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. # get_report_pivot_data Source: https://docs.qobra.co/mcp_documentation/tools/get_report_pivot_data Read a report's computed pivot-table cells: row and column groups, measures, subtotals, and paginated cell values respecting your visibility. Return a report's computed pivot-table cells — its cross-tabulated values. For a report configured as a pivot table (see `get_report`'s `pivot_configuration`): returns the row groups, the column groups, the measures, and the flattened cells (one per row-path × column-path × measure), respecting the caller's visibility. `__total__` in a path marks a subtotal or grand-total. For a flat (non-pivot) report, use `get_report_data` instead — calling this on a report with no pivot configuration raises a clear error. Grouped date values in `row_path` and `column_path` are pre-formatted period labels, not raw ISO dates. Each grouped date field is labelled by the period it covers on the company's fiscal calendar, at the group's own granularity — `Dec 2026` for a monthly group, `Q1 2026` for a quarter, `H1 2026` for a semester, `2026` for a year — in the caller's language (`Janv. 2020` / `T1 2026` / `S1 2026` in French). A pivot cannot group dates by day. Measure values in `cells` are unaffected: a date-typed measure keeps its raw ISO value. Paginated like every list tool: `limit` caps the number of cells per call, `offset` walks the pages (`next_offset` is `null` on the last one), `total_cell_count` is the full size. A page holds whole pivot rows, so it may return slightly fewer cells than `limit` (never more), and always at least one row. A pivot too large to compute on the fly raises an actionable error. You only see reports you have access to — those granted to you directly, plus any rendered as a pivot on a dashboard shared with you. ## Parameters Id of the report to read — the id list\_reports returns. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this report in the Qobra web app. The fields laid out on rows, outermost first. One of: `number`, `boolean`, `date`, `string`. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. The fields laid out on columns, outermost first. One of: `number`, `boolean`, `date`, `string`. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. One of: `number`, `boolean`, `date`, `string`. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. Value type of this measure's `__total__` cells, which can differ from `value_type` (for example a count of a string field yields a numeric total). Use this to interpret `__total__` rows and columns in `cells`. One of: `number`, `boolean`, `date`, `string`. Field type of this measure's `__total__` cells, which can differ from `field_type`. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. One cell per row-path × column-path × measure; `__total__` in a path marks a subtotal/grand-total position. Values are aligned to `row_groups`. Entries for grouped `date` fields are pre-formatted period labels (see the note above), not raw dates. Values are aligned to `column_groups`. Entries for grouped `date` fields are pre-formatted period labels (see the note above), not raw dates. Row offset for the next page — pass it back as `offset` to fetch the following cells. `null` on the last page. # get_request Source: https://docs.qobra.co/mcp_documentation/tools/get_request Read one request's full detail plus its chronological message thread: members, statements it touches, and every mention resolved by name. Return one request's full detail plus its message thread, chronologically. The natural follow-up to `list_statements`: take a `linked_requests` row and read the whole request — who is involved, which statements it touches, and the discussion. Every related entity is surfaced by name, never a bare id — inside a message too, where an @-mention reads as `@` and the people it names are listed in that message's `mentions`. A request you can't access is reported as not found. File attachments are omitted. ## Parameters Id of the request to read — the id list\_requests returns, or the one in a statement's linked\_requests. ## Response Deep link to open this request in the Qobra web app. The #-style reference shown in the UI. Admin-configured category used to classify the request; null if none. open (awaiting handling) or closed (resolved). One of: `open`, `closed`. The linked deal/record when the statement is record-based; null otherwise. The message as written, with every @-mention rendered as `@` instead of the raw user id the web app stores. See `mentions` for the ids and emails behind those names. The users @-mentioned in `content`, in order of first appearance; empty when the message mentions nobody. A mention whose id matches no user is left as a raw handle in `content` and is absent here. # get_role Source: https://docs.qobra.co/mcp_documentation/tools/get_role Return a single role with its granted permissions in plain language and the resources shared to it, so you can explain what a user may see and do — and why. Return a single role with the permissions behind it, in plain language. This is how you explain what someone can and cannot do, and why: reach for it when a user does not see something they expect, or to answer who is allowed to perform an action. Feed it the `id` from any user row's `role` (list\_users, get\_user, get\_identity), or from list\_roles. The permissions are rendered as *grants*, not as the raw matrix — a category the role is denied is left out, and inside an allowed one a yes-or-no permission the role does not hold is left out too. An absence is therefore meaningful: a category missing from the result is one the role cannot reach, which is often the answer to "why can't this user see X". The org-chart `scope` on statements, requests and commission letters is what decides *whose* records the role sees, independently of those grants. `shared_resources` is the other half of the picture: resources shared with the role individually, which reach past its category permissions. A role denied a whole category can still read specific resources through a share, so read the two together. Sharing is a live-environment concept, so those are the live ones. This tool needs the company-settings *roles* permission (READ on the Role company setting). ## Parameters Id of the role to look up — the id list\_roles returns, or the one on the `role` of any user row. ## Response Deep link to open this role in the Qobra web app. Whether this is the role newly-created users get unless another one is picked. Exactly one role of a company is the default. Whether this is the company's built-in Admin role, which holds every permission and cannot be edited. Number of non-archived users currently holding this role. The role's permissions, one entry per category it is allowed at all. A denied category is omitted entirely, so a category absent from this list is one the role cannot reach — that absence is the answer to "why can't this user see X". Coded permission category — one section of the role's matrix, matching a section of the Qobra app. One of: `statement`, `request`, `report`, `commission_letter`, `plan`, `quota`, `user`, `data_table`, `company_setting`, `audit_trail`, `sandbox`. The category's label as it reads in the web app. How far the category reaches across the org chart, for the categories that have such a setting (statements, requests, commission letters): `all` every user's, `users_below` the holder's own plus their reports', `me` their own only, `no_access` none at all — the category page opens but shows nothing. Null for a category with no org-chart scope. One of: `all`, `users_below`, `me`, `no_access`. What the role is granted inside this category. Only grants are listed: a yes-or-no permission the role does not hold is absent, so an empty list on a category that has sub-permissions means access to the page and nothing more. The request category has no sub-permissions at all — its scope is the whole grant. Coded sub-permission. A value repeats across categories with a different meaning (`role` is the Roles settings page under `company_setting`, a user's assigned role under `user`), so read it together with the category it sits in. The sub-permission's label as it reads in the web app's role settings. How far the granted permission goes. `view` (read), `edit` (write), `full` (write plus delete). One of: `view`, `edit`, `full`. What the role may do, in the web app's own wording — 'Can view' / 'Can edit' / 'Can edit and delete' for a graded permission, an action phrase such as 'Can lock' for a yes-or-no one. Resources shared with this role individually, on top of what `permissions` grants by category. This is the other half of the answer to 'why can this user see X': a role denied a category can still reach specific resources through a share. Empty when nothing is shared with the role. Live environment only. The shared resource itself — a saved view, or the report or dashboard that was shared. What kind of resource is shared: `statement`, `request`, `quota` or `data_table` for a saved view of that resource, `report` or `dashboard` for the object itself. The resource the view filters, when it belongs to one — the data table or quota it is a view of. Null for a resource that stands alone. The level this share grants the role on this resource specifically — `read` (view it), `write` (edit and re-share it) or `delete` (also remove it), each level including the ones before it. One of: `read`, `write`, `delete`. # get_sales_coach_discussion Source: https://docs.qobra.co/mcp_documentation/tools/get_sales_coach_discussion Read the full transcript of one Sales Coach discussion: each question, its answer, the Qobra data tools the agent ran, and any escalations. Return the full transcript of one Sales Coach discussion. A list\_sales\_coach\_discussions row plus every message in order — each question, the answer it got, the Qobra data tools the agent ran to ground that answer — and the escalations that opened a request. Reach for it to diagnose a single conversation: what was asked, what the agent answered, and where it fell back to a human. Reserved to full statement access, like list\_sales\_coach\_discussions. A discussion you cannot reach reads exactly like one that does not exist. ## Parameters Id of the discussion to read — the id returned by list\_sales\_coach\_discussions. ## Response Deep link to the statement the discussion is about, in the Qobra web app. Discussions have no page of their own. The user who asked the questions. The statement a discussion is about — the subject every question is asked against. Statement period, YYYY-MM. The user the statement belongs to. Usually also the discussion's author (a rep asking about their own statement), but an admin or manager can question someone else's statement. The opening question, verbatim — what the user came for. Null for a discussion with no user message yet. Number of questions the user asked. Excludes the replies they gave to the agent's own clarifying questions, which are stored as user messages too. Total messages in the transcript, questions and answers together. Number of times the agent escalated to a human, opening a request. Above zero means the agent could not answer on its own — see the `escalations` array below for detail. When the discussion was opened. Last activity on the discussion. The full transcript in chronological order — each question and the answer it got, with the tools that produced it. Who wrote the message: 'user' (the question asked), 'assistant' (the agent's answer) or 'system'. Tool traffic is folded into the answer's `tools` rather than surfacing as its own message. One of: `user`, `assistant`, `system`, `tool`. Names of the Qobra data tools the agent ran to produce this answer, in call order — how it grounded the reply. Empty on a user message, and on an answer that needed no lookup. The human hand-overs the agent opened, in order. The request the escalation opened, named after the escalation title. Null when you have no access to that request — the escalation still happened. # get_statement Source: https://docs.qobra.co/mcp_documentation/tools/get_statement Audit one statement end to end: its commission lines, complete activity log, validation state, draw balance, and its linked requests. Get the full, audit-grade detail of one statement you can access. Use this to audit a single statement end-to-end — drill into the summary rows `list_statements` returns. It returns one minimal row per commission line (drill into a line's variables and scopes with `get_statement_compensation`), the complete activity log, the validation state, the draw balance, and the linked requests. Identify the statement by `statement_id` — the id `list_statements` returns. The headline `total_amount` is converted into the currency named by `currency_mode` (payment by default) and labelled with it, so amount and currency always agree. The `conversion` block reports what that took — the currency the statement stores its amounts in and the rate applied — so a converted figure can be justified without guessing; `list_exchange_rates` holds the company's rates per period. A company with no rate configured for the requested currency is refused rather than served in another one. The per-line compensation breakdown stays in the plan's calculation currency. A statement you can't access reads exactly like one that doesn't exist, so the error never reveals a statement outside your scope. ## Parameters Id of the statement to open — the id list\_statements returns. Which currency total\_amount is converted into and labelled with: 'payment' (default) — the currency the user is paid in; 'calculation' — the currency the plan computes in; 'company' — the company's global currency. The amount and its currency always agree. One of: `company`, `payment`, `calculation`. ## Response Deep link to open this statement in the Qobra web app. Overall processing state: error (calculation failed), ongoing (open, not yet locked), locked (frozen, not yet paid), paid (sent to payroll). A paid statement is always frozen too. Distinct from the per-period reporting stage of list\_statement\_periods. One of: `error`, `ongoing`, `locked`, `paid`. Net amount to be paid out on this statement, expressed in `currency` — already reflects draw and manual payments, so it is not just the sum of the compensation lines. The ISO currency code that total\_amount is expressed in. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. When the statement last finished computing and was up to date; null if it never reached that state. True if at least one amount was manually adjusted. The groups (by dimension) this user belongs to on the statement. Deep link to open this group in the Qobra web app. The group's stable key — the identifier used to match it when synced from an integration, distinct from its display name. The dimension the group belongs to (the kind of grouping, e.g. 'Country' or 'Team'). Null if the dimension is unknown. Number of users currently in the group (attributions active as of today). Past and future members are excluded. Opening balance carried over from the previous period. Balance set manually (overwrite); null if not overridden. The draw applied this period. True when the balance was entered or edited by hand. Balance once this period's draw is applied. Workflow step this event records: freeze (statement locked), validation (an intermediate, informational sign-off), payment (sent to payroll). One of: `freeze`, `validation`, `payment`. One minimal row per commission line. Drill into a line with get\_statement\_compensation for its variables and scopes. Deep link that opens this line in the Qobra web app — its statement, with this compensation selected. The two aggregated lines (manual payments, draw) carry no `compensation_id`; the statement selects each by its own flag instead. Identifier to pass to get\_statement\_compensation for the full breakdown (variables, scopes). Null for a manual payment or draw that has no underlying compensation. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. The comment the actor wrote, with every @-mention rendered as `@` instead of the raw user id the web app stores; null when the activity carries no comment. See `comment_mentions` for the ids and emails behind those names. The users @-mentioned in `comment`, in order of first appearance; empty when it mentions nobody. A mention whose id matches no user is left as a raw handle in `comment` and is absent here. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. The statement's detailed calculation errors, resolved to variable / quota / scope names. Populated only for callers who can manage compensations or quotas (mirroring the web app's admin-only error panel); empty for anyone else and whenever the statement has no calculation error. The compensation the error sits on — feed its id to get\_statement\_compensation for the variable breakdown behind it. The variable whose computation errored. The scope the errored variable belongs to, when the error is on a scope record rather than a compensation variable. The kind of calculation error: * FormulaError / DivisionZeroError: the variable's formula could not be computed. * TypeError: a value had an unexpected type. * ConversionError: no exchange rate is configured for a currency in the period. * NullQuotaError / NullQuotaProjectionError: a quota has no value for the period. * ScopeTooLargeError: a scope returned too many records to compute. * FieldDependencyError / FieldReferenceError / DependencyError: the variable depends on another field/variable that is missing or itself in error. One of: `FormulaError`, `DivisionZeroError`, `TypeError`, `ConversionError`, `NullQuotaError`, `NullQuotaProjectionError`, `ScopeTooLargeError`, `FieldDependencyError`, `FieldReferenceError`, `DependencyError`. A human-readable specifics line for the error — the missing currency, quota, scope or dependency by name — or null when the error type carries no extra detail. How every amount on this statement reached `currency` — the currency it was stored in and the rate applied. The currency the statement stores its amounts in — its calculation currency, the one its plan computes in. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. The exchange rate applied to every amount here: an amount in `source_currency` times this rate gives the amount in `currency`. 1 when both are the same currency, i.e. nothing was converted. Read list\_exchange\_rates for the company's rates and the periods they apply to. # get_statement_compensation Source: https://docs.qobra.co/mcp_documentation/tools/get_statement_compensation Get the calculated breakdown of one commission line on a statement: every variable value, admin overrides, plan, periodicity, and scopes. Get the full calculated breakdown of one compensation on a statement. A compensation is one commission line of a statement. `get_statement` returns each line as a minimal row; this returns the *whole* displayable variable breakdown behind its amount — every variable with its `value` (and, when an admin overrode it, the `value_before_override`) — plus the compensation's plan, periodicity, ranking and its scopes (drill into their frozen records with `list_statement_records`). Hidden intermediate variables and the calculation rules themselves (formulas, tiers) are not exposed. Identify the statement by `statement_id` (from `list_statements` / `get_statement`), then the line within it by `compensation_id`. A statement or compensation you can't access reads exactly like one that doesn't exist, so the error never reveals anything outside your scope. ## Parameters Id of the statement the compensation sits on — the id list\_statements or get\_statement returns. Id of the compensation to open, from the `compensation_id` of the line in get\_statement's compensations. ## Response Deep link that opens this line in the Qobra web app — its statement, with this compensation selected. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. True when the compensation or its line errored. How often the compensation is calculated, on the company's fiscal calendar. EACH\_MONTH every month; EACH\_MONTH\_EXCEPT\_END\_OF\_QUARTER every month but the one each quarter closes on; EACH\_QUARTER, EACH\_SEMESTER and EACH\_YEAR at each period's close; Q1-Q4 once a year, at that quarter's close only. One of: `EACH_MONTH`, `EACH_MONTH_EXCEPT_END_OF_QUARTER`, `EACH_QUARTER`, `Q1`, `Q2`, `Q3`, `Q4`, `EACH_YEAR`, `EACH_SEMESTER`. The compensation's scopes (its calculation perimeters), those the statement displays — a scope the plan hides is excluded, as hidden variables are. Drill into a scope's frozen records with list\_statement\_records. Deep link that opens this scope in the Qobra web app — its statement, on its compensation line, scrolled to this tab. The scope's short label within the compensation (A, B, …). Every displayable variable of the compensation with its value — the full calculated breakdown behind the amount. Hidden (intermediate) variables are excluded. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. The variable's value (final, after any manual override). True when an admin manually overrode the computed value. The computed value before the manual override. Null unless `overwritten` is true. Set only for AMOUNT variables — the statement's calculation currency; null otherwise. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. When `error` is true, the kind of computation error (e.g. DivisionZeroError, DependencyError, TypeError); null otherwise. # get_user Source: https://docs.qobra.co/mcp_documentation/tools/get_user Return one user's full profile: identity, integrations, managers, group and plan attributions, custom attributes, and membership windows. Return the full profile of a single user in your company. Beyond identity (name, email, role, currencies, status), the result joins in the user's integrations (with the connector's human name), managers, group attributions, compensation-plan attributions and custom user attributes. Membership windows (manager / group / plan) are `YYYY-MM` month strings; a null end / departure date means the assignment is still ongoing. Each attribute is rendered human-readable — amounts carry their currency, dates are ISO-8601 instants in UTC, picklists render as labels and user references as `{id, email, name}` objects rather than raw ids — and an unset attribute surfaces as null. Each attribute here is *this user's* value. For the schema behind them — each attribute's type, whether it is synced from a source system, and the labels a picklist accepts — call [`list_user_attributes`](/mcp_documentation/tools/list_user_attributes). `shared_resources` is the half of "why can this user see X" that their role does not carry: views, reports and dashboards shared with them *individually*. A user whose role denies a whole category can still reach specific resources this way, so read it alongside [`get_role`](/mcp_documentation/tools/get_role) on their `role` id rather than concluding from the permissions alone. The rows follow the same shape `get_role` returns for a role's shares. Sharing is a live-environment concept, so those are the live ones. Reading someone else's shares is administrative information: rows for a source you are not allowed to read the sharing of are dropped, and a caller with no such permission on any source gets an empty list. Calling `get_user` on yourself always returns your own shares in full. ## Parameters Id of the user to look up — the id list\_users returns. An exact email is accepted too. ## Response Deep link to open this user in the Qobra web app. User lifecycle state: pending (invited, not logged in yet), active, inactive (deactivated), archived (has left the company; hidden by default in list\_users). One of: `inactive`, `pending`, `active`, `archived`. The user's permission role — what decides what they may see and do, not their status. Pass its id to get\_role to read the permissions behind it. The user's direct manager today; null when they report to no one. `managers` below lists their full manager history with dates. Currency the user is actually paid in. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. Currency the user's commissions are computed in. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. Whether the user holds a paid Qobra license (a seat). Only licensed users have their compensations calculated and a statement to view — an unlicensed user's statement is never computed, which is the usual reason someone 'can't see X's statement'. A newly-arrived user typically needs a license added; a departed user who no longer appears in the data is a candidate for archiving to free their seat. True when the user is NOT licensed yet has compensations that would otherwise be calculated — the statement can't be computed for lack of a seat. This is what raises the red warning banner at the top of a statement; resolve it by assigning the user a license. The user's links to the company's connected integrations (HRIS, CRM…), one entry per integration they are matched in. An empty list means the user is matched in none — which is what 'this user is missing from the \ sync' looks like. Use list\_users to answer sync-coverage questions over the whole company in one call; never call get\_user per user. The company connection this link belongs to. The user's id in the customer's own system. When this user's identity was last refreshed from the HRIS integration. Null means never synced — either the company has no HRIS connected, or this user has never been matched in it. YYYY-MM; the month the management started. YYYY-MM; null while the management is still ongoing. The dimension the group belongs to; null if unknown. YYYY-MM; the month the user joined the group. YYYY-MM; null while the membership is still ongoing. YYYY-MM; the month the user joined the plan. YYYY-MM; null while the attribution is still ongoing. Id of the attribute this value belongs to — the id [`list_user_attributes`](/mcp_documentation/tools/list_user_attributes) returns, so a value maps back to its definition even when two attributes share a display name. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. Resources shared with this user *individually*, on top of whatever their role grants — call [`get_role`](/mcp_documentation/tools/get_role) with their `role` id for the other channel, which covers everyone holding that role. Read the two together: this is what makes someone see a view their role alone would deny, so a permission answer built on the role only can be wrong. Empty when nothing is shared with them individually — or, on a user other than yourself, when nothing shared with them belongs to a resource you may administer the sharing of. Same shape as [`get_role`](/mcp_documentation/tools/get_role)'s `shared_resources`. Live environment only. The shared resource itself — a saved view, or the report or dashboard that was shared. What kind of resource is shared: `statement`, `request`, `quota` or `data_table` for a saved view of that resource, `report` or `dashboard` for the object itself. The resource the view filters, when it belongs to one — the data table or quota it is a view of. Null for a resource that stands alone. The level this share grants on this resource specifically — `read` (view it), `write` (edit and re-share it) or `delete` (also remove it), each level including the ones before it. One of: `read`, `write`, `delete`. # list_audit_logs Source: https://docs.qobra.co/mcp_documentation/tools/list_audit_logs List your company's audit-trail entries, newest first: data edits, rule changes, statement adjustments, and exports, with actor filters. List your company's audit-trail entries, most recent first. The audit trail records every action taken on the platform — data edits, compensation-rule changes, statement adjustments, exports — one entry per action, each with who did it, when, and on which resource. Combine the filters (AND) to zoom in: actor\_id='\' for one person's actions, or resource\_id=\ + after='-P1M' for a resource's recent history. Fetch get\_audit\_log for the per-field change payload of a single entry. Only the last 12 months are queryable — older entries predate the current change-tracking and are not returned. ## Parameters Keep only logs about the resource with this exact id. Omit for every resource. Keep only actions performed by the user with this id — the id list\_users returns, or their exact email. Omit for every actor. Keep only logs with this exact event name, e.g. 'RecordUpdated' — the coded action that happened. Omit for every event. Keep only logs at or after this instant. Accepts an absolute ISO-8601 date/time ('2026-07-01') or a duration relative to now ('-P7D' for the last 7 days). Omit for no lower bound. Keep only logs at or before this instant. Same accepted forms as after. Omit for no upper bound. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link that opens this audit-log entry's detail modal in the Qobra web app. When the action happened (UTC, ISO-8601). Coded name of the audited event, e.g. 'UserUpdated' or 'CompensationRuleUpdated' — the precise action taken. Id of the affected resource; null when not applicable. Human-readable name of the affected resource, if known. ObjectId of the actor; feed it to get\_user for detail. Actor's email as recorded at action time — the identifier to recognize the user by. Null for a system/script action. Actor's role name as recorded at action time. The surface the action was performed through: `Qobra UI` is the web app, its in-app AI agents (the Analyst, the Architect) included; `Qobra MCP` is an MCP client acting as the actor; `Qobra Onboarding` is the self-serve onboarding agent. Null on entries written before the surface was recorded: unknown, not the web app. One of: `Qobra UI`, `Qobra MCP`, `Qobra Onboarding`. Deep link that reopens this exact search in the Qobra web app's audit trail — the applied filters (resource, actor, event, date range) encoded as query params. Hand it to the user to let them browse the same results in the UI. # list_commission_letters Source: https://docs.qobra.co/mcp_documentation/tools/list_commission_letters List a commission-letter campaign's letters page by page, one row per recipient with envelope status, signers, pre/post signers and CC. List the letters of one commission-letter campaign, one row per recipient. The per-recipient side of `get_commission_letters_campaign`: each row carries the letter's e-signature envelope (its status and when it reached it, the ordered signers), the pre/post signers the campaign sets for that recipient and the people in CC. Every person is surfaced by email, never a bare id. Rows follow the campaign's recipient order. A campaign you can't access is reported as not found. ## Parameters Id of the campaign whose letters to list — the id list\_commission\_letters\_campaigns returns. Keep only the letters in this signature state: awaiting\_signature (sent, waiting\_for\_approval, received or viewed: a signer still has to act), signed, or action\_needed (declined, voided or expired). A letter whose e-signature envelope is still being created counts as awaiting\_signature; one of a draft or e-mail campaign matches none. Omit for every letter. One of: `awaiting_signature`, `signed`, `action_needed`. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Pass the next\_offset returned by the previous call, or None for the first page. ## Response The user this letter is for. E-signature state of this letter: sent, waiting\_for\_approval, received, viewed, signed, declined, voided, expired. Null when the letter has no envelope yet: the campaign is still a draft, was sent by e-mail, or its e-signature envelope is still being created. One of: `sent`, `waiting_for_approval`, `received`, `viewed`, `signed`, `voided`, `expired`, `declined`. When the envelope reached its current status. The ordered signers of this letter's envelope; empty without an envelope. Signer state: sent, received, delivered (means the letter was viewed), completed (signed), resent (reminder sent), declined. One of: `sent`, `received`, `delivered`, `completed`, `resent`, `declined`. When this signer reached their current status. Who the campaign has sign this letter before its owner. Who the campaign has sign this letter after its owner. People in copy of this letter. For email they are copied when the letter is sent; for DocuSign they are notified after all signers complete. They do not sign. # list_commission_letters_campaigns Source: https://docs.qobra.co/mcp_documentation/tools/list_commission_letters_campaigns List commission-letter campaigns as summaries with a derived overall status and per-envelope letter counts for e-signature and e-mail sends. List your company's commission-letter campaigns as ready-to-judge summaries. A campaign is one batch send of commission letters, built from a template for a set of recipients. Each row is a summary — never per-recipient detail — carrying everything needed to answer "where is this campaign?" without recomputing anything: a derived `overall_status` and `envelope_counts` (letters tallied per e-signature envelope status, already aggregated). Campaigns sent by e-mail have no envelopes, so their `envelope_counts` is empty. Rows are ordered most-recently-created first. You see every campaign in the company. ## Parameters Free-text filter, matched case-insensitively as a substring of the campaign name or of its ObjectId. The campaign's overall status. One of draft (not sent), generating (letters still being generated), sent (sent by e-mail), awaiting\_signatures (e-signature, some letters still pending), completed (every letter signed) or action\_needed (at least one letter declined, voided or expired). One of: `draft`, `generating`, `sent`, `awaiting_signatures`, `completed`, `action_needed`. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this campaign in the Qobra web app. How the letters are delivered: email (plain email) or esignature (electronic signature). One of: `email`, `esignature`. One of: `draft`, `generating`, `sent`, `awaiting_signatures`, `completed`, `action_needed`. Letters tallied per e-signature envelope status; empty for e-mail campaigns (they have no envelopes). # list_dashboards Source: https://docs.qobra.co/mcp_documentation/tools/list_dashboards List your company's reporting dashboards with name search and pagination, scoped to the dashboards shared with you or your full dashboard access. List your company's reporting dashboards, with an optional name filter and pagination. Each row summarizes one dashboard: its name, description, and when it was last updated. Dashboards live under the reports section; a dashboard groups visualizations of existing reports — call `get_dashboard` for a dashboard's tiles, their layout, and the reports they render. You only see the dashboards you have access to — every one of the company's at full dashboard access, otherwise only those shared with you (by role or individually); the rest are omitted. ## Parameters Filter matched against the dashboard name (case- and accent-insensitive substring) or, when it is a valid ObjectId, against the dashboard id. Id of the sandbox to read instead of the live environment — the id list\_sandboxes returns. Omit it to read the live environment, the company's real compensation data, which is the right choice unless the question is explicitly about a sandbox. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this dashboard in the Qobra web app. # list_data_table_records Source: https://docs.qobra.co/mcp_documentation/tools/list_data_table_records List one data table's records with human-readable cell values plus metadata: name, source, external id, tag, active flag, and timestamps. List one data table's records, each column rendered human-readable. A data table is a company object table (deals, opportunities, accounts, custom objects…); a record is one row. Each record carries its id, its cell `values`, and record metadata: `name` (the table's name column), `source` (origin), `source_id` (external system id), `tag` (status), `active`, `created_at` and `last_modified_at`. Cell values never expose raw ids — amounts carry their currency, dates are ISO-8601 instants in UTC, picklists resolve to labels, user references to a structured `{id, email, name}` object and linked records to their name. **A record's `values` are positional against `data_table.fields`** — the Nth value is the Nth field, so read that header to know a cell's name and type. The column's name and type are stated once there rather than repeated on every cell, which keeps a page of a wide table small. You only see tables and records you have access to; a table you can't access is reported as not found. When a table reaches you through several shared views, pass `view_id` to pick which one. Rows are ordered most-recently-created first for local tables; for integration-synced tables they are ordered alphabetically by record name. The result carries a list-level `application_url` that reopens the exact list you just fetched — the table's page with the chosen view selected. It is only set when a view was passed; otherwise it is null and the table's own `resource_url` already opens the page. ## Parameters Id of the table to read — the id list\_data\_tables returns for a customer-facing (local or integration) table. Free-text filter, matched the same way as the records page — a case- and accent-insensitive substring of the record's name and external id. Omitted returns the table's records unfiltered. AND-ed with the applied view's row filter. Id of the view to narrow the records to, from the `views` of the table's row in list\_data\_tables. Required only when several shared views reach the table. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this data table in the Qobra web app. One of: `kombo`, `salesforce`, `pipedrive`, `microsoft_dynamics`, `hubspot`, `odoo`, `zoho`, `attio`, `postgresql`, `gcp-postgresql`, `snowflake`, `snowflake-keys`, `redshift`, `microsoft-azure-sql`, `microsoft-azure-synapse`, `bigquery`, `s3`, `sftp`, `netsuite`, `public_api`. The views the caller may query for this table's records — pass one's id as `view_id` to list\_data\_table\_records to narrow the rows. Empty when no view is relevant. Stable technical key for this field in Qobra's public API (use it as a durable reference; the display name can change). Field data type: amount, percentage, float, string, bool, date, picklist, multipicklist, user, or object (a reference to another data table — see linked\_to). One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. When type is object, the table this field points to; null otherwise. Name of the linked table. `null` when the caller has no access to the linked table (for example, when the link is visible only through a shared view on the current table). `id` is always returned. Whether create\_record / update\_record can set this column. False for a lookup, whose value is read from the linked record, and for a link to another table. Deep link that opens this record's detail in the Qobra web app — its table's page, focused on this row. When a `view_id` was passed, the link carries it too so the record opens against that same view. Where the record comes from: integration (synced from a connected system) or local (created in Qobra). One of: `integration`, `local`, `qobra`, `reporting`. The record's id in its source system. Integration record status (Created / Overwritten / Archived). Null for local-table records (no integration baseline) and for untouched integration rows. The record's cell values, one per column, in the same order as the enclosing result's data\_table.fields — read the Nth value against the Nth field to know its name and type. A cell carries only what varies per row (the value, plus its currency for an amount); the column's name and type are stated once in that header rather than repeated on every cell. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. Deep link that reproduces this list in the web app — the table's page with the chosen view selected. Null when no view filter was applied (the table's own resource\_url already opens the page). # list_data_tables Source: https://docs.qobra.co/mcp_documentation/tools/list_data_tables List your company's local and integration-synced data tables with their fields, public-API keys, data types, and linked-table references. List your company's data tables and their fields, with filters and pagination. Only local and integration-synced tables are returned. Each table carries its source, its public-API key, the integration it syncs from for integration tables, and its fields. Every field carries its public-API key in `.` form, its data type and — for a link field pointing at another table — that table's id and name. You only see the data tables you have access to. Rows are ordered alphabetically by table name (A→Z). ## Parameters Filter matched against the table name (case- and accent-insensitive substring) or, when it is a valid ObjectId, against the table id. Filter restricting results to local or integration tables. Omit to return both kinds. Keep only tables synced from the integration with this id — the id list\_integrations returns. Local tables have no integration, so this excludes them. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Pass the next\_offset returned by the previous call, or None for the first page. ## Response Deep link to open this data table in the Qobra web app. One of: `kombo`, `salesforce`, `pipedrive`, `microsoft_dynamics`, `hubspot`, `odoo`, `zoho`, `attio`, `postgresql`, `gcp-postgresql`, `snowflake`, `snowflake-keys`, `redshift`, `microsoft-azure-sql`, `microsoft-azure-synapse`, `bigquery`, `s3`, `sftp`, `netsuite`, `public_api`. The views the caller may query for this table's records — pass one's id as `view_id` to list\_data\_table\_records to narrow the rows. Empty when no view is relevant. Each view also carries an `access` roster — roles and users who can read it — populated when you may share this resource, `null` otherwise. Who can read this view, split into roles and users, each with the level they hold. Only the tools that list views for you to choose between resolve it — list\_quotas, list\_data\_tables and get\_identity — and only for callers who may share the resource. Null everywhere else, so null is silence about the roster (either not asked for, or not yours to see), never an empty one: a caller who may see it and finds nobody gets empty `roles` and `users` instead. The level this role holds on the whole resource category, which reaches every view of it without a share — `read` (view it), `write` (edit and re-share it) or `delete` (also remove it), each level including the ones before it. Null when the role only got this view through a share. One of: `read`, `write`, `delete`. The level granted to this role on this view specifically — `read` (view it), `write` (edit and re-share it) or `delete` (also remove it), each level including the ones before it. Null when the role reaches the view through its category access rather than a share. One of: `read`, `write`, `delete`. The level this view is shared with the user at, individually, on top of whatever their role already grants — `read` (view it), `write` (edit and re-share it) or `delete` (also remove it), each level including the ones before it. One of: `read`, `write`, `delete`. Stable technical key for this field in Qobra's public API (use it as a durable reference; the display name can change). Field data type: amount, percentage, float, string, bool, date, picklist, multipicklist, user, or object (a reference to another data table — see linked\_to). One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. When type is object, the table this field points to; null otherwise. Name of the linked table. `null` when the caller has no access to the linked table (for example, when the link is visible only through a shared view on the current table). `id` is always returned. Whether create\_record / update\_record can set this column. False for a lookup, whose value is read from the linked record, and for a link to another table. # list_dependencies Source: https://docs.qobra.co/mcp_documentation/tools/list_dependencies List everything that references a quota, data table, group, group dimension, plan, report or integration, so you know what breaks before you change it. Answer "what breaks if I change or delete this?" for one resource. The tool returns everything that references the resource — compensations and their plans, the plans a group is attributed to, reports, dashboards, saved views, variables, lookup fields, plan simulation pipelines, and the data tables and quotas an integration syncs — each as a uniform row naming the dependent, the object it lives inside, and the sandbox it belongs to (null on live). Reach for it before advising on a change to a quota, data table, group, group dimension, plan, report or integration, and to answer "is this still used?" — an integration whose `total` is 0 is the one the web app badges as unused. For an integration the answer covers its synced data tables and quotas, the same two kinds the web app's own dependencies dialog lists. It does not cover the users, user attributes and groups an HRIS connection syncs, which disconnecting would unsync too. So a `total` of 0 on an integration is not on its own a verdict that disconnecting it is safe. Not every dependent blocks a deletion. A group's plan attributions (`plan_attribution` rows) do not: deleting the group removes it from those plans instead, so its members leave them and their statements change. Check for these rows before deleting a group. One call returns every dependent — there is no pagination and no cap, because a partial impact analysis is worse than none. `counts_by_type` rolls the same rows up per nature so you can read the shape of the impact at a glance, and a `total` of 0 means nothing references the resource, so changing or deleting it breaks nothing. Dependents are reported in full even when you could not open one yourself: withholding one would read as "nothing uses this", which is the wrong conclusion to act on. Each dependent carries only its id and name, never its contents. ## Permissions Reading a resource's dependents needs **full read access** to that kind of resource — a resource merely shared with you does not admit you, since the question exists to be asked before changing or deleting it. The tool is visible as soon as your role can read *any* of the seven resource types, but each call is checked against the permission the `resource_type` argument selects: you can only analyze the kinds you may read. Analyzing an `integration` needs read access to company settings' integrations. Analyzing a `report` additionally requires the reporting feature to be enabled for your company. This tool is read-only: it says what *would* break and changes nothing. ## Parameters What kind of resource `resource_id` names. One of: `quota`, `data_table`, `group`, `group_dimension`, `plan`, `report`, `integration`. Each kind is read under its own permission, so you can analyze only the kinds you may read. Id of the resource to analyze — the id the matching list tool returns (list\_quotas, list\_data\_tables, list\_groups, list\_group\_dimensions, list\_plans, list\_reports, list\_integrations). An id, never a name. ## Response The kind of resource that was analyzed. One of: `quota`, `data_table`, `group`, `group_dimension`, `plan`, `report`, `integration`. The resource whose dependents were analyzed. Link to this resource's page in the Qobra web app, stamped with its sandbox when it lives in one. Null for a group dimension, which is managed inside the users page and has no page of its own. Total dependents across every nature — the length of `dependencies`. Zero means nothing references this resource, so changing or deleting it breaks nothing. Per-nature rollup of `dependencies`, to read the shape of the impact at a glance. Natures with no dependent are omitted. Dependents of this nature. A nature absent from the list has none. Every dependent, ordered by nature then name. The whole set, never a page and never capped — `total` is always its length. The nature of the dependency — how the dependent uses the analyzed resource. `compensation`: a commission rule reads it. `compensation_used_as_scope`: a compensation consumed as another one's scope. `dashboard` / `report`: a reporting object renders it. `table`: a data table matches users on it. `group`: a group belonging to the dimension, which blocks deleting it. `plan_attribution`: a plan the group is attributed to. It does not block deleting the group, but the deletion removes the group from that plan, so its members leave the plan and their statements change. `scope`: a compensation's scope. `data_table` / `quota`: a resource synced from the integration. `lookup_field`: a field looking it up on another table. `scope_variable` / `statement_variable` / `sync_variable`: a variable computing from it. `commission_letter`: a letter campaign. `object_view` / `quota_view` / `statement_view` / `request_view`: a saved view filtering on it. `public_api`: the public API matches users on it. `plan_simulation_pipelines`: a plan's simulation pipeline. `record_report_dependency`: a record report tracks it. The dependent object itself. Null for the two natures that name no object — `public_api` and `record_report_dependency`, where the dependency is the mechanism rather than a row you can open. The object the dependent lives inside, when its nature has one: the plan for a compensation or a simulation pipeline, the data table for an object view or a lookup field, the quota for a quota view. Null when the dependent stands alone. The sandbox this dependent lives in, or null when it lives in the live environment. A dependent in a sandbox does not affect live compensation. # list_exchange_rates Source: https://docs.qobra.co/mcp_documentation/tools/list_exchange_rates Read the company's exchange rates per date range: what currency was applied, at what rate, and since when — the source of every converted amount. List the exchange rates your company converts amounts with, per date range. Call this to justify a converted figure. A statement is computed in its calculation currency and reported in the currency you asked for, so every amount another tool returns has passed through one of these rates; this is where you read which. Also answers what a company can pay or calculate in, and since when. A range applies from its start month until the next one begins — the last one is open-ended. Rates are set per range, so a date earlier than the earliest range has no rate at all: that is the ConversionError a statement reports, and asking for such a `date` returns no range rather than an error. Companies keep a handful of ranges, so the full list comes back unpaginated. ## Parameters Optional. Keep only the range that applies on this day — an ISO-8601 date like '2026-03-14', or a duration relative to now like '-P1Y'. Omitted, every range is returned. ## Response The company's own currency — the base every rate is expressed against. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. The date ranges of exchange rates, oldest first. Empty when the company configured none, or when the requested period falls before the earliest range. Deep link that opens this date range in the Qobra web app — the currencies settings page, browsed to this range. First month these rates apply, `YYYY-MM` — the month [`update_exchange_rates`](/mcp_documentation/tools/update_exchange_rates) takes as `start_month`. A month before the earliest range has no rate at all. Last month these rates apply, `YYYY-MM` — the month before the next range starts. Null on the current range, which has no end. One rate per currency configured on this range, ordered by currency code. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. How many units of this currency one unit of the company currency buys, over this date range (the company currency's own rate is 1). To convert between any two currencies of the same range, take the ratio: an amount in the source currency times `rate` of the target divided by `rate` of the source — the formula the app itself applies. # list_group_dimensions Source: https://docs.qobra.co/mcp_documentation/tools/list_group_dimensions List your company's group dimensions, such as Country or Team, each with its current group count, ordered alphabetically by dimension name. List the group dimensions of your company, with filters and pagination. A dimension is a *kind* of grouping — 'Country', 'Team', 'Segment' — and each holds one group per value (the 'Country' dimension holds one group per country). Reach for this to discover which dimensions exist before filtering list\_groups by one; each row carries the dimension's current group count. Rows are ordered alphabetically by dimension name (A→Z). ## Parameters Filter matched against the dimension name (case-insensitive substring) or, when it is a valid ObjectId, against the dimension id. When True, return only archived dimensions; otherwise only active (non-archived) dimensions. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response The dimension's stable key — the identifier used to match it when synced from an integration, distinct from its display name. Number of groups that belong to this dimension. # list_groups Source: https://docs.qobra.co/mcp_documentation/tools/list_groups List your company's groups by dimension with filters and pagination: each row carries the group's dimension and its current member count. List the groups of your company, with filters and pagination. Groups are a secondary hierarchy organized by dimensions (a 'Country' dimension holds one group per country, and so on). Each row carries the group's dimension and its current member count; use get\_group for the full member list. Rows are ordered alphabetically by group name (A→Z). ## Parameters Filter matched against the group name (case-insensitive substring) or, when it is a valid ObjectId, against the group id. Keep only groups in the dimension with this id — the id list\_group\_dimensions returns. When True, return only archived groups; otherwise only active (non-archived) groups. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this group in the Qobra web app. The group's stable key — the identifier used to match it when synced from an integration, distinct from its display name. The dimension the group belongs to (the kind of grouping, e.g. 'Country' or 'Team'). Null if the dimension is unknown. Number of users currently in the group (attributions active as of today). Past and future members are excluded. # list_help_center_articles Source: https://docs.qobra.co/mcp_documentation/tools/list_help_center_articles List Qobra Help Center articles as summary rows with id, title, and author-written description, ready for a full-body follow-up read. List Qobra's Help Center articles; returns summary rows + pagination. The Help Center is Qobra's public product documentation (synced from Intercom). Use it to ground answers about how Qobra works, then call `get_help_center_article` with a row's `id` (or name) to read the full body. Each row carries the article `id` (for the follow-up call), the `title`, the author-written `description` (`None` when the article has none — there is no generated summary), the `url` and `updated_at`. Rows are otherwise ordered alphabetically by title. Results are scoped to the caller's in-app access: articles in a collection the caller isn't entitled to are omitted. ## Parameters Keyword filter, matched case-insensitively as a substring of the article title or body. Results are ranked by relevance (most matches first). Omit it to list every article, alphabetically by title. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response The author-written description. Public URL of this Help Center article (an external help.qobra.co link, not a Qobra web-app page); null if unpublished. # list_imports Source: https://docs.qobra.co/mcp_documentation/tools/list_imports List your company's data imports, filtered by resource type, status, dates or errors, each with a deep link to the run in the Qobra web app. List your company's data imports, most recent first. An import is one run of Qobra pulling data in — a CRM/integration refresh, a CSV upload or an API push — into a data table, a quota or the user directory. Each row summarizes one run: its status, what triggered it, which resource types it touched, an overall progress, an error flag and a warning flag. Combine the filters (AND) to zoom in, e.g. resource\_type='quota' + status='finished', or error=True to triage failed runs. Fetch get\_import for the per-batch breakdown (record counters, error detail) of a single run. Only the last 12 months are queryable — older imports are stale and not returned. You only see imports that touched a resource type you may read, and filtering by a resource\_type outside your permissions is refused with a permission error. To find out why one table or one connection fails to sync, pass its `data_table_id` or `integration_id` with `error=True`. One scheduled refresh imports every table of a connection, so the error is judged on the batches those ids select, not on the whole run. Then call [`get_import`](/mcp_documentation/tools/get_import) on a returned run and read the failed batch's `error_category`, `error_type` and `error_description`. `error_category` tells you why the connection failed (for example `authentication` means you need to reconnect it, `rate_limit` means you should retry later). ```json theme={null} { "integration_id": "6650f1c2a8b4e1d2c3f4a5b6", "error": true, "created_after": "-P7D" } ``` A run can also finish without an error and still drop rows: a users push skips a duplicated line, or a sync rejects a value it cannot map. Pass `warning=True` to find those runs, then read the batch's `rejected_rows` in [`get_import`](/mcp_documentation/tools/get_import). Each rejected row comes with its reason and a locator, such as the CSV line it came from or the user it is about. That is where you answer "why didn't my users come in?". ```json theme={null} { "resource_type": "user", "warning": true, "created_after": "-P7D" } ``` ## Parameters Keep only imports that touched this resource type: 'data\_table', 'quota' or 'user'. Omit for every type. Keep only imports in this state: 'scheduled', 'started' or 'finished'. Omit for every state. Keep only imports created at or after this instant. Accepts an absolute ISO-8601 date/time ('2026-07-01') or a duration relative to now ('-P7D' for the last 7 days). Omit for no lower bound. Keep only imports created at or before this instant. Same accepted forms as created\_after. Omit for no upper bound. Keep only imports with a batch for the data table with this id — the id list\_data\_tables returns. Omit for every table. Keep only imports with a batch synced from the integration with this id — the id list\_integrations returns. Omit for every integration. True keeps only imports with at least one failed batch; False keeps only imports where every batch succeeded. Only the batches you can read count, and with data\_table\_id or integration\_id only the ones they select: a run where another table failed is not in error for this one. Omit for both. True keeps only imports with a batch that rejected or skipped rows, even if it did not fail; False keeps only imports with none. Counted like error. Omit for both. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link that opens this import expanded in the Qobra web app, on the history page of a resource type it touched. Null only for an import with no batch yet, which no history page lists. Overall status: 'scheduled', 'started' or 'finished'. One of: `scheduled`, `started`, `finished`. What kicked the import off, e.g. 'scheduled\_refresh', 'integration\_refresh' or 'quota\_sync'. Null when unknown. One of: `scheduled_refresh`, `integration_refresh`, `integrations_with_frequency_refresh`, `braqoffice_refresh`, `users_creation`, `user_integration_sync`, `user_file_import_refresh`, `user_attributes_creation`, `groups_creation`, `data_table_refresh`, `data_table_settings_update`, `field_creation`, `quota_creation`, `quota_sync`, `quota_refresh`, `quota_settings_update`. Overall completion 0–100, averaged across the import's batches (100 when it has no batch). True when a batch you can read ended in error; get\_import says which and why. True when a batch you can read finished but rejected or skipped some rows; get\_import lists them. A run can carry a warning without an error. Distinct resource types the import touched — any of 'data\_table', 'quota', 'user'. The user who triggered the import; null for a system or scheduled import. # list_integrations Source: https://docs.qobra.co/mcp_documentation/tools/list_integrations List the systems your company syncs data from, each with its id, type, category, and paused state, ready to filter data tables by integration. List the systems your company syncs data from. An integration is one connection to an external system — a CRM, an HRIS, a database, a spreadsheet — whose data lands in Qobra as data tables. Call this to learn which connections exist, then pass one's id as the `integration_id` filter of list\_data\_tables to see only its tables. Each row says whether the connection is `in_use` — whether a data table or a non-archived quota is synced from it. To see *what* depends on one, chain its id into list\_dependencies with `resource_type='integration'`. Neither counts the users, attributes and groups an HRIS connection syncs, so read `in_use: false` as "no table and no quota", not as "safe to disconnect". To diagnose a sync that fails, chain its id into [`list_imports`](/mcp_documentation/tools/list_imports) as `integration_id` with `error=True`. To narrow to one table, pass a table's `data_table_id` from list\_data\_tables instead. Then call [`get_import`](/mcp_documentation/tools/get_import) on a returned run for the failed batch's error detail. Rows are ordered by connection name. The public-API connection is listed too, since tables pushed through the API carry its id like any other. Integrations pending deletion are never returned. Takes no arguments. ## Response One of: `kombo`, `salesforce`, `pipedrive`, `microsoft_dynamics`, `hubspot`, `odoo`, `zoho`, `attio`, `postgresql`, `gcp-postgresql`, `snowflake`, `snowflake-keys`, `redshift`, `microsoft-azure-sql`, `microsoft-azure-synapse`, `bigquery`, `s3`, `sftp`, `netsuite`, `public_api`. Deep link that opens this connection in the Qobra web app — the integrations settings page, focused on it. HRIS and API connections still resolve to the plain settings page: they are listed there but have no per-connection dialog to open. What kind of system this connects to — crm, hris, database, spreadsheet, api, and so on. One of: `file_system`, `database`, `crm`, `hris`, `api`. True when the integration is paused: it keeps its tables and their records, but stops refreshing them. True when at least one data table or non-archived quota is synced from this connection — the inverse of the UNUSED badge the web app shows on its card. It counts those two kinds only: an HRIS connection syncing users, their attributes and their groups reads as false while still feeding all three, which disconnecting would unsync. So false means no table and no quota depend on it, never that disconnecting it is safe. Call list\_dependencies with `resource_type='integration'` for the resources behind it. # list_plans Source: https://docs.qobra.co/mcp_documentation/tools/list_plans List the compensation plans of your company with filters and pagination: each row carries the plan's section and its compensation count. List compensation plans of your company, with filters and pagination. Each plan carries its section and its compensation count; open a plan with get\_plan for each compensation's periodicity, date range and the reporting metrics it feeds, plus the plan's members. The rest of the calculation behind a compensation is not exposed there — read a statement's computed breakdown with get\_statement\_compensation. Rows are ordered alphabetically by plan name (A→Z). ## Parameters Filter matched against the plan name (case-insensitive substring) or, when it is a valid ObjectId, against the plan id. When True, return only archived plans; otherwise only live (non-archived) plans. Deleted plans are never returned. Id of the sandbox to read instead of the live environment — the id list\_sandboxes returns. Omit it to read the live environment, the company's real compensation data, which is the right choice unless the question is explicitly about a sandbox. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this plan in the Qobra web app. Folder/group this plan is filed under (a concept shared with reports); null if unfiled. How many compensations the plan holds. Open the plan with get\_plan for each compensation's periodicity, date range and the reporting metrics it feeds, plus the plan's members. The rest of the calculation behind a compensation is not exposed there — read a statement's computed breakdown with get\_statement\_compensation. # list_quota_values Source: https://docs.qobra.co/mcp_documentation/tools/list_quota_values List one quota's per-user, per-period target values with pagination, including each value's owner, period, and currency for amount quotas. List one quota's target values across users and periods, with pagination. Resolve a single quota, then return its per-user, per-period target values. The quota's `type` and `frequency` are surfaced once in the `quota` header; each row carries `value`, the owning `user` (an `{id, email, name}` reference), the `period` (big-endian, at the quota's frequency), and `currency` only when the quota is amount-typed. Pair with `list_quotas` to find the quota first. Filters combine with AND. You only see quotas and values you have access to; a quota you can't access is reported as not found. Pass `view_id` to narrow to a single view's rows. Rows are ordered by period ascending (earliest first), then by user. Each row exposes a `resource_url` pointing at its parent quota. The result also carries a list-level `application_url` that reopens the exact list you just fetched — the quota's page with the chosen view selected. It is only set when a view was passed; otherwise it is null and the quota's own `resource_url` already opens the page. ## Parameters Id of the quota to read values from — the id list\_quotas returns. Single period, in the quota's own frequency format: YYYY-MM (monthly), YYYY-QX (quarterly), YYYY-SX (semesterly) or YYYY (annually). A period in the wrong format for this quota's frequency is rejected with the expected format. Omitted: values for every period. User filter — a name/email substring or an ObjectId, matched the same way as list\_users search. Filters the values to the matching users. Omitted: every user you can see. Id of the quota view to restrict the rows to its users, from the `views` of the quota's row in list\_quotas. Omitted: every user you can see. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this quota in the Qobra web app. Unit of the target value: amount (monetary, has a currency), percentage, or float (plain number). One of: `amount`, `percentage`, `float`. The quota's default currency for amount targets: 'company' (the company currency), 'user' (each user's payment currency) or 'calculation' (the user's calculation currency). A target value that carries its own currency overrides this default. Only meaningful for amount quotas. One of: `company`, `user`, `calculation`. The quota views the caller may query for this quota's values — pass one's id as `view_id` to list\_quota\_values to narrow the rows. Empty when no view is relevant. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. Deep link to the quota this value belongs to (a quota value has no page of its own, so it points at its parent quota). Deep link that reproduces this list in the web app — the quota's page with the chosen view selected. Null when no view filter was applied (the quota's own resource\_url already opens the page). # list_quotas Source: https://docs.qobra.co/mcp_documentation/tools/list_quotas List your company's quotas with their measurement type, frequency, archived flag, and the saved views usable to narrow their quota values. List your company's quotas, with filters and pagination. Each quota carries its measurement type (amount / percentage / float), its frequency (monthly / quarterly / semesterly / annually) and whether it is archived. You only see the quotas you have access to. Each quota also lists the `views` whose id you can pass as `view_id` to `list_quota_values` to narrow the rows. Rows are ordered alphabetically by quota name (A→Z). ## Parameters Filter matched against the quota name (case- and accent-insensitive substring) or, when it is a valid ObjectId, against the quota id. When True, return only archived quotas; otherwise only active (non-archived) quotas. Id of the sandbox to read instead of the live environment — the id list\_sandboxes returns. Omit it to read the live environment, the company's real compensation data, which is the right choice unless the question is explicitly about a sandbox. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Pass the next\_offset returned by the previous call, or None for the first page. ## Response Deep link to open this quota in the Qobra web app. Unit of the target value: amount (monetary, has a currency), percentage, or float (plain number). One of: `amount`, `percentage`, `float`. The quota's default currency for amount targets: 'company' (the company currency), 'user' (each user's payment currency) or 'calculation' (the user's calculation currency). A target value that carries its own currency overrides this default. Only meaningful for amount quotas. One of: `company`, `user`, `calculation`. The quota views the caller may query for this quota's values — pass one's id as `view_id` to list\_quota\_values to narrow the rows. Empty when no view is relevant. Each view also carries an `access` roster — roles and users who can read it — populated when you may share this resource, `null` otherwise. Who can read this view, split into roles and users, each with the level they hold. Only the tools that list views for you to choose between resolve it — list\_quotas, list\_data\_tables and get\_identity — and only for callers who may share the resource. Null everywhere else, so null is silence about the roster (either not asked for, or not yours to see), never an empty one: a caller who may see it and finds nobody gets empty `roles` and `users` instead. The level this role holds on the whole resource category, which reaches every view of it without a share — `read` (view it), `write` (edit and re-share it) or `delete` (also remove it), each level including the ones before it. Null when the role only got this view through a share. One of: `read`, `write`, `delete`. The level granted to this role on this view specifically — `read` (view it), `write` (edit and re-share it) or `delete` (also remove it), each level including the ones before it. Null when the role reaches the view through its category access rather than a share. One of: `read`, `write`, `delete`. The level this view is shared with the user at, individually, on top of whatever their role already grants — `read` (view it), `write` (edit and re-share it) or `delete` (also remove it), each level including the ones before it. One of: `read`, `write`, `delete`. # list_reports Source: https://docs.qobra.co/mcp_documentation/tools/list_reports List your company's reports with filters and pagination: each row carries a report's name, type, tracked data table, section, and access. List your company's reports, with filters and pagination. Each row summarizes one report: its name, type, description, the tracked data table for record reports, and the section it is filed under. Callers with write access on reports also get who can view each report (`access` — the roles, including roles with default access such as admins, and the users it is shared with); read-only callers get `access: null`. A report's internals — fields, filters, pivot-table and chart configuration — are intentionally not returned. You only see the reports you have access to; the rest are omitted. Rows are ordered alphabetically by report name (A→Z). ## Parameters Filter matched against the report name (case- and accent-insensitive substring) or, when it is a valid ObjectId, against the report id. Filter restricting results to statement or record reports. Omit to return both kinds. One of: `statement`, `record`. Id of the sandbox to read instead of the live environment — the id list\_sandboxes returns. Omit it to read the live environment, the company's real compensation data, which is the right choice unless the question is explicitly about a sandbox. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this report in the Qobra web app. What the report is built on: statement (payout statements) or record (the rows of the tracked\_datatable). One of: `statement`, `record`. Source data table tracked by a record report, with its id and name; null for statement reports. The dashboard tiles this report can render: `chart` when a chart is configured on it, `pivot_table` when a pivot table is. Empty when neither is — the report then has no tile to show on a dashboard. The section the report is filed under (id and name), or null when unfiled. Who can view the report, split into roles and users. Each entry carries its id, name, and access level. Only returned to callers with write access on reports; null for read-only callers. One of: `read`, `write`, `delete`. One of: `read`, `write`, `delete`. One of: `read`, `write`, `delete`. # list_requests Source: https://docs.qobra.co/mcp_documentation/tools/list_requests List the requests you can access with simple filters and pagination, ordered by most recent activity, each with a direct resource URL. List the requests you can see, with simple filters and pagination. A request can gather members, an assignee and a topic, and link statements. You only see the requests you have access to — those you're a member of, plus any shared with you. Rows are ordered by most recent activity first (last modification date). Each row exposes a `resource_url` that opens that single request. The result also carries a list-level `application_url` that reopens the requests board on the same view and status tab you just queried. Because the board is always scoped to one status tab, this link needs a `status`; it is null without one, and null when the served set matches no single board view. ## Parameters Either a built-in token — "assigned\_to\_me" or "my\_requests" (the ones you created) — or the id of a request view shared with you, never its name. Omitted: every request you can access. Free-text filter matched against the request name, its full id, or its reference (the # reference shown in the UI is the last 8 characters of the id; a leading # is accepted). Status filter. One of open or closed. One of: `open`, `closed`. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this request in the Qobra web app. The #-style reference shown in the UI. Admin-configured category used to classify the request; null if none. open (awaiting handling) or closed (resolved). One of: `open`, `closed`. The linked deal/record when the statement is record-based; null otherwise. Deep link that reproduces this list in the web app — the requests board opened on the served view and status tab. Null when `status` was omitted (the board shows one status tab at a time, so a link cannot faithfully reproduce a multi-status result), and null when the served set matches no single board view. # list_roles Source: https://docs.qobra.co/mcp_documentation/tools/list_roles List your company's permission roles alphabetically with a name/id filter and pagination; each row carries the role's headcount, default and admin flags. List your company's permission roles and how many users hold each. A role is what decides what a user may see and do; use `get_role` for the permissions behind one. Reach for this to answer questions about the shape of the organization's access — how many people are admins, which roles exist, which one new users get by default. Rows are ordered alphabetically by role name (A→Z). Qobra support's own god role is never listed, and the headcount counts non-archived users only. This tool needs the company-settings *roles* permission (READ on the Role company setting). The web app does not require it for a caller merely reading role names, but an organization's permission structure is administrative data, and gating it like `get_role` means a caller who can list a role can always read it. ## Parameters Filter matched against the role name (case-insensitive substring) or, when it is a valid ObjectId, against the role id. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this role in the Qobra web app. Whether this is the role newly-created users get unless another one is picked. Exactly one role of a company is the default. Whether this is the company's built-in Admin role, which holds every permission and cannot be edited. Number of non-archived users currently holding this role. # list_sales_coach_discussions Source: https://docs.qobra.co/mcp_documentation/tools/list_sales_coach_discussions List Sales Coach discussions, newest first, to review what users asked the Qobra AI assistant and which topics the agent had to escalate. List the company's Sales Coach discussions, most recently opened first. A discussion is one conversation a user had with the Sales Coach — the Qobra AI assistant they ask about their own statement — their questions and the agent's answers. Reach for it to review what people actually asked the AI: recurring confusion about a plan, topics the agent had to escalate, a specific rep's questions. Qobra's other AI agents have no equivalent tool yet, so this is not the full picture of what people ask the product's AI. Reserved to full statement access, so it returns every discussion in the company. Filters combine with AND. Each row is a summary — the opening question, how many questions followed, whether the agent escalated; fetch get\_sales\_coach\_discussion for the full transcript of one. Keep paginating for as long as next\_offset comes back set: a page can be shorter than limit — empty, even — without being the end of the list. ## Parameters Keep only the discussions this person had with the agent — the id list\_users returns, or their exact email. Omit for every author. Keep only discussions about the statement with this id — the id list\_statements returns. Combined with period, it narrows to the intersection, which is empty unless that statement belongs to that period. Keep only discussions about statements of this period, in YYYY-MM format (e.g. 2026-03). Omit for every period. Keep only discussions opened at or after this instant. Accepts an absolute ISO-8601 date/time ('2026-07-01') or a duration relative to now ('-P7D' for the last 7 days). Omit for no lower bound. Keep only discussions opened at or before this instant. Same accepted forms as start. Omit for no upper bound. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Pass the next\_offset returned by the previous call, or None for the first page. ## Response Deep link to the statement the discussion is about, in the Qobra web app. Discussions have no page of their own. The user who asked the questions. The statement a discussion is about — the subject every question is asked against. Statement period, YYYY-MM. The user the statement belongs to. Usually also the discussion's author (a rep asking about their own statement), but an admin or manager can question someone else's statement. The opening question, verbatim — what the user came for. Null for a discussion with no user message yet. Number of questions the user asked. Excludes the replies they gave to the agent's own clarifying questions, which are stored as user messages too. Total messages in the transcript, questions and answers together. Number of times the agent escalated to a human, opening a request. Above zero means the agent could not answer on its own — fetch get\_sales\_coach\_discussion for the escalation detail. When the discussion was opened — the sort key, most recent first, and what the start/end filters match. Last activity on the discussion. # list_sandboxes Source: https://docs.qobra.co/mcp_documentation/tools/list_sandboxes List your company's sandboxes, copies of its real compensation data, and get the ids to pass as the sandbox_id argument of other tools. List your company's sandboxes — copies of its real compensation data. A sandbox lets you design and test compensation without touching the real data: it has its own plans, quotas, statements and reports. Call this to learn which sandboxes exist, then pass one's id as the `sandbox_id` argument of another tool to read that sandbox. Every tool reads the company's real data unless you pass such an id, so you only need this when the question is explicitly about a sandbox. Rows carry no deep link: the web app has no sandbox page — it switches from its own selector. Takes no arguments; deleted sandboxes are never returned. ## Response Pass this id as the `sandbox_id` argument of another tool to read that tool's resources inside this sandbox. Whether the sandbox holds usable data. A freshly created one is still being duplicated and reads as not ready; passing its id to another tool is refused until it flips. Whether reports and dashboards can be read in this sandbox. False for a sandbox created without reporting, or whose reporting duplication is not published yet — the report tools refuse such a sandbox while the other tools still serve it. First period the sandbox computes statements for, as `YYYY-MM`. Null when the sandbox carries no period window. Last period the sandbox computes statements for, as `YYYY-MM`. Outside this window the sandbox holds no statement. # list_statement_periods Source: https://docs.qobra.co/mcp_documentation/tools/list_statement_periods List statement totals broken down by period, currency, and reporting stage, with summed amounts and statement counts per row, newest first. List how your statements break down, one row per period, currency and stage. Returns one row per period (`YYYY-MM`, newest first), currency and reporting `stage` (ERROR / OPEN / VALIDATED / LOCKED / CLOSED), each with the summed `total_amount` in that `currency` and how many statements it covers. Use it to answer "how are we managing our statements?" — e.g. whether every past period is already CLOSED. Amounts are never summed across currencies, so a period spans several rows when its statements do. Only the statements you have access to are counted — the same set `list_statements` returns. Amounts are converted into the currency named by `currency_mode` (each statement's payment currency by default). To compare totals across periods, pass `currency_mode='company'` so every period reports in the single company currency you can line up directly — the default (`payment`) can split one period across several currency rows, which is not comparable period to period. ## Parameters When True (default), only periods from the last 12 months (relative to today) are returned. When False, the full history is returned. Which currency each total\_amount is converted into and labelled with: 'payment' (default) — the currency the user is paid in; 'calculation' — the currency the plan computes in; 'company' — the company's global currency. The amount and its currency always agree. Prefer 'company' when comparing or aggregating totals across users or periods. One of: `company`, `payment`, `calculation`. Id of the sandbox to read instead of the live environment — the id list\_sandboxes returns. Omit it to read the live environment, the company's real compensation data, which is the right choice unless the question is explicitly about a sandbox. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. ## Response One row per (period, currency, stage) combination. Ordered by period (newest first), then currency, then stage. The period in `YYYY-MM` format. Deep link that opens the statements board on this period in the Qobra web app. The ISO currency code that `total_amount` is expressed in. Amounts are never summed across currencies, so a period appears once per currency present in its statements. The statement reporting stage: * ERROR: the statement's calculation errored out and needs attention. * OPEN: created, not yet in the validation workflow. * VALIDATED: in the validation workflow, awaiting freeze. * LOCKED: frozen, not yet paid. * CLOSED: sent to payroll. One of: `ERROR`, `OPEN`, `VALIDATED`, `LOCKED`, `CLOSED`. Sum of `total_amount` over the statements in this period, currency and stage — never summed across currencies. How many statements fall in this period, currency and stage. # list_statement_records Source: https://docs.qobra.co/mcp_documentation/tools/list_statement_records List the frozen records of one compensation scope on a statement: the snapshot rows that fed the commission, with displayable cell values. List the frozen records of one compensation scope on a statement. A compensation calculates over one or more scopes (a data table filtered to a perimeter); each scope's records are frozen into the statement at calculation time. This returns those snapshot records — the deals/rows that fed the commission — each with its displayable cell values (amounts in the statement's calculation currency, picklists as labels, user references as `{id, email, name}` objects, linked records by name). This is the frozen snapshot, not the live data table (use list\_data\_table\_records for the current data). **A record's `values` are positional against the result's `fields`** — the Nth value is the Nth column, so read that header to know a cell's name and type. The column's name and type are stated once there, and the shared `calculation_currency` once at the result level, rather than repeated on every cell. A column a record has no value for holds a null, so every row is the same width as the header. Identify the statement by `statement_id` (from `list_statements` / `get_statement`) and the scope by `scope_id` (from `get_statement_compensation`'s scopes). A scope belongs to exactly one compensation, so its id is enough — there is no compensation argument. A statement or scope you can't access reads exactly like one that doesn't exist, so the error never reveals anything outside your scope. Records keep the statement's frozen scope order — the index they were snapshotted in at calculation time. ## Parameters Id of the statement the scope was frozen into — the id list\_statements or get\_statement returns. Id of the scope to read, from the `id` of one of get\_statement\_compensation's scopes. Free-text filter, a case-insensitive substring matched against each record's text and linked-record name fields. Omitted returns the scope's records unfiltered. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response The scope's displayable columns (name + type), listed once for the whole page — each record's `values` align positionally against this list (the Nth value belongs to the Nth field). One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. The currency every AMOUNT value in the records is in. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. The record's display name (its name-field value), falling back to its source id then its record\_id when no name field exists. The record's cell values, one per column, in the same order as the result's `fields` — read the Nth value against the Nth field to know its name and type. Amounts are in the result's `calculation_currency`, stated once there rather than on every cell. A column this record has no value for is null. # list_statements Source: https://docs.qobra.co/mcp_documentation/tools/list_statements List the statements you can access with simple filters, pagination, and a currency mode to aggregate comparable totals in one currency. List the statements you can see, with simple filters and pagination. You only see the statements you have access to. To aggregate or compare a single comparable total across users or periods, pass `currency_mode='company'` so every amount lands in one currency, and report that single total — never sum amounts in different currencies. The default (`payment`) leaves each amount in the user's own pay currency, which is not comparable across a multi-currency team. Each row's `conversion` block reports the currency its amounts were stored in and the rate applied, so a converted figure can be justified (`list_exchange_rates` holds the company's rates per period). A statement whose company has no rate for the requested currency is refused rather than served in another one. Each row exposes a `resource_url` that opens that single statement. The result also carries a list-level `application_url` that reopens the statements board on the queried period. It is always set because `period` is required. ## Parameters Period in YYYY-MM format (e.g. 2026-03). Required. Free-text filter matched against the statement's user (name or email), same matching as list\_users search. Status filter. One of ongoing (open / not yet locked), locked (frozen but not paid), paid (sent to payroll), or error (the calculation errored out). The error condition is bundled into this single field. If True, only statements with at least one manual amount adjustment (overwritten). If True, only statements that have at least one linked request you can access. Keep only statements computed from the compensation plan with this id — the id list\_plans returns. Id of the statement view to narrow the rows to its users, from get\_identity (the views of your statement access). A filtering aid only: it never widens what you can see. Id of the manager whose team to narrow to — everyone below them in the hierarchy (all levels, not just direct reports). The id list\_users returns, or the manager's exact email. It only filters within what you can already see — a teammate outside your access stays hidden. Which currency each total\_amount is converted into and labelled with: 'payment' (default) — the currency the user is paid in; 'calculation' — the currency the plan computes in; 'company' — the company's global currency. The amount and its currency always agree. One of: `company`, `payment`, `calculation`. Id of the sandbox to read instead of the live environment — the id list\_sandboxes returns. Omit it to read the live environment, the company's real compensation data, which is the right choice unless the question is explicitly about a sandbox. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Pass the next\_offset returned by the previous call, or None for the first page. ## Response Deep link to open this statement in the Qobra web app. Overall processing state: error (calculation failed), ongoing (open, not yet locked), locked (frozen, not yet paid), paid (sent to payroll). A paid statement is always frozen too. Distinct from the per-period reporting stage of list\_statement\_periods. One of: `error`, `ongoing`, `locked`, `paid`. Net amount to be paid out on this statement, expressed in `currency` — already reflects draw and manual payments, so it is not just the sum of the compensation lines. The ISO currency code that total\_amount is expressed in. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. When the statement last finished computing and was up to date; null if it never reached that state. How every amount on this statement reached `currency` — the currency it was stored in and the rate applied. The currency the statement stores its amounts in — its calculation currency, the one its plan computes in. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. The exchange rate applied to every amount here: an amount in `source_currency` times this rate gives the amount in `currency`. 1 when both are the same currency, i.e. nothing was converted. Read list\_exchange\_rates for the company's rates and the periods they apply to. Deep link that reproduces this list in the web app — the statements board opened on the queried period. Always present, since `period` is required. # list_user_attributes Source: https://docs.qobra.co/mcp_documentation/tools/list_user_attributes List the schema of user attributes your company defines: name, type, picklist options, readonly flag and source connection, in display order. List the *schema* of user attributes your company defines — what it records about its people (Segment, Hiring date, Region…), of what type, and which labels a picklist accepts. This is the schema behind the `attributes` array [`get_user`](/mcp_documentation/tools/get_user) returns: reach for `list_user_attributes` when you need each attribute's type, whether it is synced from a source system, or the exact labels a picklist accepts; reach for `get_user` when you need one specific user's values. Rows come in the company's own display order (the order shown in the Qobra web app), so a page is directly usable as a form or a rendering layout. Access is gated by **READ** permission on **Users**. ## Parameters Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Id of the attribute — pair it with the `id` each `get_user` attribute entry carries to map a user's value back to its definition, even when two attributes share a display name. What the company says this attribute means, in their own words. Null when they wrote none. Attribute data type: amount, percentage, float, string, bool, date, picklist, multipicklist, or user (a reference to another Qobra user). One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`. Selectable option labels for a PICKLIST / MULTIPICKLIST attribute, in definition order; null for every other type. Archived options are left out — they can no longer be picked, though a user set to one before it was archived still carries it, so a value `get_user` returns need not appear in this list. True when the value cannot be edited in Qobra — it is owned by the source system that syncs it. The company connection this attribute is synced from; null when it is maintained in Qobra itself. An attribute whose source connection has since been removed reads as maintained in Qobra rather than naming a link that no longer syncs. # list_users Source: https://docs.qobra.co/mcp_documentation/tools/list_users List the users of your company alphabetically with filters and pagination; rows carry integration links and last HRIS sync for coverage checks. List users of your company, with simple filters and pagination. Rows are ordered alphabetically by name (A→Z). Each row already carries the user's current direct manager, integration links and last HRIS sync. To group users by manager, or to answer a sync-coverage question ("who is missing from the HiBob sync?"), paginate this tool and read `manager` or `integrations`. Never call get\_user once per user. Reach for get\_user only when you need one user's manager history, groups, plans or custom attributes. ## Parameters Free-text filter matched against the user's normalized search field (name + email, lowercased, unidecoded). Status filter. One of inactive, pending, active, archived. Defaults to non-archived users. One of: `inactive`, `pending`, `active`, `archived`. Id of the manager whose team to narrow to — everyone below them in the hierarchy (all levels, not just direct reports). The id list\_users returns, or the manager's exact email. With manager\_id, keep only the people reporting directly to that manager, not the levels below them. Requires `manager_id`; passing it alone returns an input error. Defaults to `false`. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this user in the Qobra web app. User lifecycle state: pending (invited, not logged in yet), active, inactive (deactivated), archived (has left the company; hidden by default in list\_users). One of: `inactive`, `pending`, `active`, `archived`. The user's permission role — what decides what they may see and do, not their status. Pass its id to get\_role to read the permissions behind it. The user's direct manager today; null when they report to no one. A user has one manager at a time, so grouping users by manager takes one paginated list\_users, never a call per manager. get\_user lists their past managers with dates. Currency the user is actually paid in. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. Currency the user's commissions are computed in. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. Whether the user holds a paid Qobra license (a seat). Only licensed users have their compensations calculated and a statement to view — an unlicensed user's statement is never computed, which is the usual reason someone 'can't see X's statement'. A newly-arrived user typically needs a license added; a departed user who no longer appears in the data is a candidate for archiving to free their seat. True when the user is NOT licensed yet has compensations that would otherwise be calculated — the statement can't be computed for lack of a seat. This is what raises the red warning banner at the top of a statement; resolve it by assigning the user a license. The user's links to the company's connected integrations (HRIS, CRM…), one entry per integration they are matched in. An empty list means the user is matched in none — which is what 'this user is missing from the \ sync' looks like. Use list\_users to answer sync-coverage questions over the whole company in one call; never call get\_user per user. The company connection this link belongs to. The user's id in the customer's own system. When this user's identity was last refreshed from the HRIS integration. Null means never synced — either the company has no HRIS connected, or this user has never been matched in it. # split_record Source: https://docs.qobra.co/mcp_documentation/tools/split_record Split a data table record between several users — a deal shared by two reps. Split a record between several users — a deal shared by two reps. Each part becomes a new record named after the original ("\ - Split \#1", …): a copy of it, with its own user in `user_field_id` and its own values for the numeric fields listed. The original is archived. Nothing checks that the parts add up to the original, as in the web app, so send values that do. Call get\_data\_table first for the id of the user field and of the numeric fields to split, and for its `adjustment_reasons`. Statements are not recalculated by this call, just as when a record is split in the web app. They pick the parts up at the next automatic recalculation, or when someone refreshes them. Requires write permission on data tables. Not available on demonstration companies. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Id of the data table holding the record — the id list\_data\_table\_records was called with. Id of the record to split, in that table — the id list\_data\_table\_records returns. Id of the user field that says whose each part is — the deal owner, typically. A field of type user from get\_data\_table. The parts, at least two: one new record per part. Id or email of the user this part goes to, as a user reference carries them. The numeric fields this part overrides. A field left out keeps the original record's value. Id of a numeric field (amount, float or percentage) of the record — a field id get\_data\_table returns. This part's value for the field: a number for an amount or a float, and the ratio for a percentage (0.8 for 80%). Why the record is split — one of the table's `adjustment_reasons` from get\_data\_table. Required only when that list is not null; omit it otherwise. Optional note kept in the records' history. The call is refused, with a message naming the problem, when no data table has the `data_table_id`, when no record of that table has the `record_id`, when the record is already archived, when fewer than two parts are passed, when `user_field_id` is not a user field of the table, when a part lists a field that is not numeric or lists a field twice, or when the table requires a `reason` and none from its `adjustment_reasons` was passed. ## Example ```json theme={null} { "data_table_id": "665f1c2e8a4b3d0012a7e9f1", "record_id": "665f1c2e8a4b3d0012a7ea10", "user_field_id": "665f1c2e8a4b3d0012a7e9f6", "splits": [ { "user_id": "665f1c2e8a4b3d0012a7eb01", "values": [{ "field_id": "665f1c2e8a4b3d0012a7e9f3", "value": 9000 }] }, { "user_id": "665f1c2e8a4b3d0012a7eb02", "values": [{ "field_id": "665f1c2e8a4b3d0012a7e9f3", "value": 6000 }] } ], "reason": "Shared deal", "comment": "Co-sold by two account executives." } ``` ## Response The table of the records, whose `fields` header names their values in order — the same row list\_data\_tables returns. See [`list_data_tables`](/mcp_documentation/tools/list_data_tables) for the full field breakdown. The records created, one per part, in the order sent — the same rows list\_data\_table\_records returns. See [`list_data_table_records`](/mcp_documentation/tools/list_data_table_records) for the full field breakdown. The record that was split, now archived. # update_dashboard Source: https://docs.qobra.co/mcp_documentation/tools/update_dashboard Rename a dashboard, add reports to it and arrange its tiles, in one call. Rename a dashboard, add reports to it, arrange its tiles — any of the three, or all of them in one call. Pass `name` and/or `description` to change how the dashboard reads. Pass `reports` to add reports to it as the tiles each one can render — a report carrying both a chart and a pivot table becomes two tiles, one carrying only a chart becomes one, and one carrying neither is skipped, there being nothing to show. A report's `visualization_types` narrows that to its chart or its pivot table when you want only one of them; `list_reports` names what each report can render in its `available_visualizations`. Removing a tile is still a job for the Qobra web app. Pass `layout_rows` to say how the tiles sit on the page. It describes the **whole** arrangement, top to bottom, and it is applied after the reports are added — so one call can add reports and place them, naming each by its `report_id` since a new tile has no `visualization_id` yet. A tile you leave out is not removed: it keeps its place, appended on a row of its own. An empty list resets the page, every tile on a row of its own. Pass at least one of `name`, `description`, `reports` or `layout_rows` — a call with none of them has nothing to update. **Read the `outcome` on every `tile_changes` row before reporting success.** Adding never fails over one bad report: it adds what it can and says so per row. A report can come back `not_found`, `no_visualization` when it has no chart or pivot table yet, `type_not_available` when it has none of the types you asked for, or `already_added`. **Read the `outcome` on every `layout_changes` row too.** Arranging never fails over one bad entry either: it places what it can and says so per entry. `layout` is the page as it now stands, read back from the dashboard: where every tile sits, the ones this call added included. Read it rather than assuming your rows were taken verbatim: a row holds at most three charts or two pivot tables, and one that overflows is split. You need write access on the dashboard itself. A dashboard shared with you read-only is refused with a message saying the share is read-only; pick one you can edit from [`list_dashboards`](/mcp_documentation/tools/list_dashboards), or create a new one with [`create_dashboard`](/mcp_documentation/tools/create_dashboard). A dashboard you cannot read at all is refused as not found. Available only where the Qobra write tools beta is enabled. Every write to the live environment is recorded in the audit trail as the `Qobra MCP` agent; writes to a sandbox are not audited. ## Parameters Id of the dashboard to update — the id list\_dashboards returns, or the one create\_dashboard just returned. The dashboard names the environment this writes to, so there is no sandbox argument. New display name for the dashboard. Omit to leave the current name as it is. New free-text note about what the dashboard is for. Omit to leave the current description as it is. Reports to add, each as every tile it can render — its chart, its pivot table, or both, unless its visualization\_types narrows it. Omit to leave the dashboard's tiles untouched. Id of the report to add, as list\_reports returns it. Which of its tiles to add: `chart`, `pivot_table`, or both. Omit to add every tile the report offers, which is what you want unless the caller asked for one of the two; list\_reports names what each report offers in its available\_visualizations. The dashboard's rows, top to bottom; each row lists one or more tiles left to right. An entry is a visualization\_id from get\_dashboard, or a report\_id — the report form is how you arrange tiles this same call is adding, since they have no visualization\_id until they exist, and it places every tile of that report. A row holds at most three charts or two pivot tables and is split when it overflows; a tile you leave out keeps its place, appended on a row of its own. Omit to leave the arrangement alone; pass an empty list to reset it, every tile on a row of its own. ## Example Add a report's pivot table to an existing dashboard and put it on the first row, next to a tile already on the page: ```json theme={null} { "dashboard_id": "665f1c2e8a4b3d0012a7ec10", "reports": [ { "report_id": "665f1c2e8a4b3d0012a7eb03", "visualization_types": ["pivot_table"] } ], "layout_rows": [ ["665f1c2e8a4b3d0012a7ed20", "665f1c2e8a4b3d0012a7eb03"] ] } ``` `665f1c2e8a4b3d0012a7ed20` is the `visualization_id` of a tile already on the dashboard, as [`get_dashboard`](/mcp_documentation/tools/get_dashboard) lists it. Every tile left out of `layout_rows` keeps its place on a row of its own. ## Response The dashboard as it now stands after the update. Deep link to open this dashboard in the Qobra web app. What became of each report you passed, one row per tile: a report added as both a chart and a pivot table gets a row for each, and one that adds nothing gets a single row, with visualization\_type null. Read `outcome` on each before reporting success, since a call can succeed having added nothing you asked for. Empty when no reports were passed. What became of this report. "added" for a tile this call put on the dashboard; "already\_added" for one the dashboard already carried, which is how a re-sent call reads as the no-op it was; "not\_found" when no report of yours has that id; "no\_visualization" when the report has neither a chart nor a pivot table to show; "type\_not\_available" when it has tiles but none of the visualization\_types you asked for. One of: `added`, `already_added`, `not_found`, `no_visualization`, `type_not_available`. The report id you passed, echoed back as you spelled it, so the row matches your request even when report is null. The report it resolved to, named — null when it is not one you can read, since naming it would say a report you cannot see exists. The tile this row is about: chart or pivot\_table. Null when no single tile can be named — nothing was added at all. One of: `chart`, `pivot_table`. Why, as a sentence saying what to do next. Null for a tile that landed, there being nothing to act on. The dashboard's rows as they now stand, top to bottom, each tile left to right — read back from the dashboard, so it shows where every tile actually sits. A tile this call added lands on a row of its own at the end. Empty when the dashboard has no tile yet. Rows passed in layout\_rows are applied after the reports are added and come back as the store took them: a row holds at most three charts or two pivot tables and is split when it overflows, and a tile you left out keeps its place on a row of its own. The tiles on this row, left to right. Rows read top to bottom. Id of the tile, as get\_dashboard lists it. The report this tile renders. What the tile renders the report as: chart or pivot\_table. One of: `chart`, `pivot_table`. One row per layout entry you passed, saying what became of it. Empty when no layout\_rows were passed. What became of this layout entry. "placed" for a tile this call positioned; "already\_placed" when an earlier entry of the same layout already placed it, a tile sitting on one row only; "not\_found" when the entry names neither a tile of this dashboard nor a report on it. One of: `placed`, `already_placed`, `not_found`. The layout entry you passed, echoed back. Why, as a sentence saying what to do next. Null for an entry that placed its tile. # update_exchange_rates Source: https://docs.qobra.co/mcp_documentation/tools/update_exchange_rates Set the exchange rates of one date range, creating the range if new. Send the range's whole rate table. Set the exchange rates of one date range, creating the range if new. `start_month` picks the range. The month an existing range starts in updates that range. Any later month creates a new range that applies from the first day of that month until the next range. A new range must start after the latest one. `rates` is the range's **whole** table, not a change to it: every currency the company uses must be listed, or the call is refused. Read the current rates with [`list_exchange_rates`](/mcp_documentation/tools/list_exchange_rates) first and send back the ones to keep. The company currency can be left out: its rate is always 1. A currency the company does not use yet is added to every range at the rate given, because a currency is configured company-wide. To set a different rate on an older range, call the tool again on that range. The call is refused when: * a validated statement was computed with the range's rates, which locks them (a currency new to the company can still be added). Unvalidate those statements in the Qobra web app first. * a new range would start over months a validated statement covers. * `start_month` matches no existing range and is not after the latest one. * a currency is listed twice, a rate is not greater than 0, or the company currency is given a rate other than 1. Sending rates a range already has writes nothing and returns `unchanged`, so the same call can be sent twice. Statements are not recalculated by this call. They convert with the new rates at their next recalculation. Requires write permission on company settings > currency, and multi-currency access for the company. Refused on demonstration companies. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ```json Example arguments theme={null} { "start_month": "2026-10", "rates": [ { "currency_code": "USD", "rate": 1.08 }, { "currency_code": "GBP", "rate": 0.84 } ] } ``` ## Parameters The month the range starts, `YYYY-MM`. The month an existing range starts in (the month of its `start_date` in list\_exchange\_rates) updates that range; any later month creates a new range from its first day. The range's whole table: one rate per currency the company uses, plus any currency to add. The company currency can be left out — its rate is always 1\. The currency, as a code: USD, GBP… How many units of this currency one unit of the company currency buys — the same direction list\_exchange\_rates reports. ## Response The company's own currency — the base every rate is expressed against. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. All date ranges of exchange rates after the write, oldest first. Deep link that opens this date range in the Qobra web app — the currencies settings page, browsed to this range. First month these rates apply, `YYYY-MM` — the month [`update_exchange_rates`](/mcp_documentation/tools/update_exchange_rates) takes as `start_month`. A month before the earliest range has no rate at all. Last month these rates apply, `YYYY-MM` — the month before the next range starts. Null on the current range, which has no end. One rate per currency configured on this range, ordered by currency code. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. How many units of this currency one unit of the company currency buys, over this date range (the company currency's own rate is 1). To convert between any two currencies of the same range, take the ratio: an amount in the source currency times `rate` of the target divided by `rate` of the source — the formula the app itself applies. What the call did to the range: `created` (a new range), `updated` (some of its rates changed, or it gained a currency), or `unchanged` (it already had these rates; nothing was written). Currencies the range did not carry before, now added at the rate given to every range that lacked them. To set another rate on an older range, call the tool on that range. # update_group Source: https://docs.qobra.co/mcp_documentation/tools/update_group Rename a group, declare its whole membership in one call, or both. Missing rows are removed, so send the full set to keep. Rename a group, set who is in it, or both in one call. Pass at least one of `name`, `key` or `attributions` — a call with none of them has nothing to update. `attributions` is the group's **whole** membership, not an addition to it: whoever the list leaves out is taken out of the group. Read the group with [`get_group`](/mcp_documentation/tools/get_group) first and send back every member to keep, or the ones missing from your list are removed. Omitting the argument leaves the membership untouched, so a rename alone is safe; passing an empty list empties the group. Declaring a membership that already matches changes nothing, so the same call can be sent twice. Removing a membership needs the delete permission on groups, as it does in the web app. Only what actually changed comes back in `attribution_changes`, each row saying whether it was created, moved, or removed. A membership change here recalculates the statements of the users it affects, on the live environment, wherever the group is attributed to a plan — their statements move to `NEED_REFRESH` and are recalculated in the background. A call that changes nothing triggers no refresh. Renaming propagates too: the group reads under its new name on every statement and in every report that breaks down by its dimension. Available only where the Qobra write tools beta is enabled. Every write to the live environment is recorded in the audit trail as the `Qobra MCP` agent; writes to a sandbox are not audited. ## Parameters Id of the group to update — the id list\_groups returns, or the one create\_group just returned. New display name for the group. Omit to leave the current name as it is. New stable key for the group — the identifier an integration matches it on, distinct from the display name. Omit to leave the current key as it is. Who is in the group, as the complete list — every user, each with the months they belong for. This replaces the group's membership rather than adding to it: anyone missing from the list is taken out of the group. Omit the argument to leave the membership untouched; pass an empty list to empty the group. Send one row per continuous stretch, with no month covered twice for the same user. Id or email of the user in the group, as a user reference carries them. YYYY-MM; the first month this membership covers. YYYY-MM; the last month it covers, included. Omit it for a membership that stays open with no end. ## Response The group as it now stands after the update. Deep link to open this group in the Qobra web app. The group's stable key — the identifier used to match it when synced from an integration, distinct from its display name. The dimension the group belongs to (the kind of grouping, e.g. 'Country' or 'Team'). Null if the dimension is unknown. Number of users currently in the group (attributions active as of today). Past and future members are excluded. Every membership the call actually changed — created, moved, or removed because the declared list no longer held it. Empty when the call only renamed the group, and empty when the declared membership already matched what the group had, which is how a repeated call reads as the no-op it was. `created` for a membership this call added; `moved` for one that already existed and had its dates changed, the row showing the new ones; `removed` for one the declared set no longer holds. The membership as it now stands — its last state before removal for a "removed" row, since the user is no longer in the group. # update_plan Source: https://docs.qobra.co/mcp_documentation/tools/update_plan Rename a plan, declare its whole membership in one call, or both. Missing rows are removed, so send the full set to keep. Rename a plan, set who is attributed to it, or both in one call. Pass at least one of `name` or `attributions` — a call with neither has nothing to update. `attributions` is the plan's **whole** membership, not an addition to it: whoever the list leaves out is taken off the plan. Read the plan with [`get_plan`](/mcp_documentation/tools/get_plan) first and send back every membership to keep, or the ones missing from your list are removed. Omitting the argument leaves the membership untouched, so a rename alone is safe; passing an empty list clears the plan. Declaring a membership that already matches changes nothing, so the same call can be sent twice. Only what actually changed comes back in `attribution_changes`, each row saying whether it was created, moved, or removed. On the live environment, changing a membership triggers a statement refresh for every user it affects — their statements move to `NEED_REFRESH` and are recalculated in the background. A call that changes nothing triggers no refresh, and a sandbox write causes none either. Available only where the Qobra write tools beta is enabled. Every write to the live environment is recorded in the audit trail as the `Qobra MCP` agent; writes to a sandbox are not audited. ## Parameters Id of the plan to update — the id list\_plans returns. The plan also decides which environment the attributions land in, so there is no sandbox argument here. New display name for the plan. Omit to leave the current name as it is. Who the plan attributes, as the complete list — every user and group on it, each with the months they are on it for. This replaces the plan's membership rather than adding to it: anyone missing from the list is taken off the plan. Omit the argument to leave the membership untouched; pass an empty list to clear it. Send one row per continuous stretch, with no month covered twice for the same user or group. Id or email of the user to attribute, as a user reference carries them. Fill this or group\_id, never both. Id of the group to attribute — the id list\_groups returns. Fill this or user\_id, never both. YYYY-MM; the first month this membership covers. YYYY-MM; the last month it covers, included. Omit it for a membership that stays open with no end. ## Response The plan as it now stands after the update. Deep link to open this plan in the Qobra web app. The section the plan is filed under; null when unfiled. Every membership the call actually changed — created, moved, or removed because the declared list no longer held it. Empty when the call only renamed the plan, and empty when the declared membership already matched what the plan had, which is how a repeated call reads as the no-op it was. `created` for a membership this call added; `moved` for one that already existed and had its dates changed, the row showing the new ones; `removed` for one the declared set no longer holds. The attribution as it now stands — its last state before removal for a "removed" row, since it no longer exists on the plan. `user` for an individual attribution, `group` for a whole group attributed at once. The attributed user; null for group attributions. The attributed group; null for user attributions. # update_quota_values Source: https://docs.qobra.co/mcp_documentation/tools/update_quota_values Set, change or clear target values of one quota, for one or more users. All or nothing, at most 100 values per call. Set, change or clear target values of one quota, for one or more users. Only the cells listed are written: a user or a period left out keeps its value, so this is an adjustment, never a replacement of the whole quota. Read the current values with list\_quota\_values first when the change is relative to them. Pass `value: null` to clear a cell. On a value synchronized from an integration, `null` cancels the manual overwrite and brings the synchronized value back instead. The call is all or nothing: an unknown user, a period not in the quota's frequency format or a period listed twice refuses the whole call before anything is written. A call writes at most 100 values, all users together. Statements are not recalculated by this call, just as when a target is edited in the web app — they pick the new values up at the next automatic recalculation, or when someone refreshes them. The quota's environment (live or a sandbox) is the one the quota lives in. Requires write permission on quotas. Available only where the Qobra write tools beta is enabled. Every write to the live environment is recorded in the audit trail as the `Qobra MCP` agent; writes to a sandbox are not audited. ## Parameters Id of the quota whose targets to write — the id list\_quotas returns, or the one create\_quota just returned. The targets to write, grouped by user. At most 100 values per call, all users together. Id or email of the user, as a user reference carries them. Currency of this user's amount targets, as a code (EUR, USD…). It applies to every value this user has on the quota, not only to the ones sent here. Omit it to keep the user's current currency; ignored on a percentage or float quota. The cells to write for this user, one per period. Period of the target, in the quota's own frequency format: YYYY-MM (monthly), YYYY-QX (quarterly), YYYY-SX (semesterly) or YYYY (annually). The target, in the unit list\_quota\_values returns. null clears it — on a value synchronized from an integration, null cancels the manual overwrite and brings the synchronized value back instead, since a synchronized value is never deleted. ## Example ```json theme={null} { "quota_id": "66a0b1c2d3e4f50012345678", "values": [ { "user_id": "66a0b1c2d3e4f50012340001", "currency": "EUR", "values": [ { "period": "2026-10", "value": 50000 }, { "period": "2026-11", "value": 55000 } ] }, { "user_id": "66a0b1c2d3e4f50012340002", "values": [{ "period": "2026-10", "value": null }] } ] } ``` ## Response The quota the values were written on — the same row list\_quotas returns. See [`list_quotas`](/mcp_documentation/tools/list_quotas) for the full field breakdown. Its `views` list the view ids list\_quota\_values takes as `view_id`, but not who they are shared with: each view's `access` is null here. Call list\_quotas when the question is who can read a view. Every cell this call changed. A cell sent at the value it already held is not listed, so an empty list means nothing changed. The value after this call; null when the cell was cleared. Currency code of the value, only for an amount quota. Deep link to open the quota in the Qobra web app. What happened to the cell: created (it was empty), updated (new value), cleared (the value was removed), or overwrite\_cancelled (a manual overwrite of a synchronized value was dropped, bringing the synchronized value back). One of: `created`, `updated`, `cleared`, `overwrite_cancelled`. The value before this call; null when the cell was empty. # update_record Source: https://docs.qobra.co/mcp_documentation/tools/update_record Change some cells of one data table record, archive it or restore it. Change some cells of one record, archive it or restore it. Only the fields listed change. Call get\_data\_table first for the ids, the type each value takes and `editable` (false for lookups and links, which cannot be written), and list\_data\_table\_records for the current values. On a record synced from an integration the edit is kept as an overwrite and the synced value is preserved beside it, as in the web app. An import run with the "override" policy replaces the edit with the synced value. Statements are not recalculated by this call, just as when a record is edited in the web app. They pick the change up at the next automatic recalculation, or when someone refreshes them. Requires write permission on data tables. Not available on demonstration companies. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Id of the data table holding the record — the id list\_data\_table\_records was called with. Id of the record to change, in that table — the id list\_data\_table\_records returns. The cells to change, one per field. A field left out keeps its value; `value: null` empties a cell. Omit to change only `active`. Id of the column — a field id get\_data\_table returns. Only a field whose `editable` is true can be written. The cell's new value, in the shape the read tools serve it: a number for amount / float / percentage (a percentage is the ratio, 0.8 for 80%), true/false for bool, an ISO date for date, text for string, one option label for picklist, a list of option labels for multipicklist, and a user id or email for user. null empties the cell. Currency code of an amount (EUR, USD…). Omit it to keep the cell's currency; ignored on every other type. false archives the record, true restores it. Omit to leave it as it is. Why the record is changed — one of the table's `adjustment_reasons` from get\_data\_table. Required only when that list is not null; omit it otherwise. Optional note kept in the record's history. The call is refused, with a message naming the problem, when no data table has the `data_table_id`, when no record of that table has the `record_id`, when neither `values` nor `active` is passed, when a `field_id` is unknown or listed twice, when a field is a lookup or a link to another table, when a value does not match its field's type or picklist options, or when the table requires a `reason` and none from its `adjustment_reasons` was passed. ## Example ```json theme={null} { "data_table_id": "665f1c2e8a4b3d0012a7e9f1", "record_id": "665f1c2e8a4b3d0012a7ea10", "values": [ { "field_id": "665f1c2e8a4b3d0012a7e9f3", "value": 15000, "currency": "EUR" }, { "field_id": "665f1c2e8a4b3d0012a7e9f4", "value": null } ], "reason": "Amount correction", "comment": "Amount renegotiated after signature." } ``` To archive a record without changing its values, pass only `active`: ```json theme={null} { "data_table_id": "665f1c2e8a4b3d0012a7e9f1", "record_id": "665f1c2e8a4b3d0012a7ea10", "active": false } ``` ## Response The table of the record, whose `fields` header names the record's values in order — the same row list\_data\_tables returns. See [`list_data_tables`](/mcp_documentation/tools/list_data_tables) for the full field breakdown. The record as it now exists — the same row list\_data\_table\_records returns. Its `values` are read in the order of `data_table.fields`. See [`list_data_table_records`](/mcp_documentation/tools/list_data_table_records) for the full field breakdown. What this call changed, the record's archived state included (as 'Active record'). Empty when nothing changed — the call then wrote and recorded nothing. Name of the field that changed. The value before the call, as the record's history shows it. The value after the call, as the record's history shows it. # update_request Source: https://docs.qobra.co/mcp_documentation/tools/update_request Change an existing request's title, status, assignee, topic, members or linked statements, and close or reopen it. Change an existing request: its title, status, assignee, topic, members or linked statements. Pass only what changes: an omitted argument leaves that part as it is, and a call that changes nothing writes nothing. `member_ids` and `statements` are whole sets, not additions — call get\_request first and send back everything to keep, or what is missing from your list is removed. A statement the request already links, sent back with the same record, is kept as it is. A closed request can be edited only in a call that reopens it (`status` open); a call can also make its changes and close the request in one go. The assignee and the topic can be changed here but not removed. Removing yourself from `member_ids` is allowed, and you then lose the request unless your role reaches it another way. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Example To add a member and close the request in one call, read the request with [`get_request`](/mcp_documentation/tools/get_request) first, then send back every member and statement to keep alongside the new one: ```json theme={null} { "request_id": "6650f1c2a8b4e1d2c3f4a5b6", "status": "closed", "member_ids": ["6650f1c2a8b4e1d2c3f4a001", "6650f1c2a8b4e1d2c3f4a002"], "statements": [ { "user_id": "jane.doe@example.com", "period": "2026-03" } ] } ``` Omit `member_ids` and `statements` entirely when they don't change. ## Parameters Id of the request to change — the id list\_requests or create\_request returns. New title of the request. Omit to keep it. open to reopen the request, closed to close it as resolved. Omit to keep it. One of: `open`, `closed`. Id or email of the person to hand the request to — as list\_users returns it. They become a member and are notified it is theirs. Omit to keep the assignee. Id of the topic to file the request under — one of `requests.topics` from get\_company\_settings. Omit to keep it. The request's **whole** membership — ids or emails, as list\_users returns them. Anyone left out is removed, anyone new is notified they were added, and the assignee stays a member whatever the list. Omit to leave the members as they are. The **whole** set of statements the request is about — one user in one month each, optionally narrowed to one of its records. A statement left out is unlinked, and an empty list unlinks them all. Omit to leave them as they are. Id or exact email of the user whose statement it is. Month of the statement, as YYYY-MM (e.g. '2026-03'). The one record of this statement the request is about — a deal, most often. Omit to link the statement as a whole. Id of the scope the record was frozen into — the `id` of one of get\_statement\_compensation's scopes. Id of the record — the `record_id` list\_statement\_records returns for that scope. ## Response The request as it now stands — the same row list\_requests returns, unchanged when the call changed nothing. Deep link to open this request in the Qobra web app. The #-style reference shown in the UI. Admin-configured category used to classify the request; null if none. open (awaiting handling) or closed (resolved). One of: `open`, `closed`. The linked deal/record when the statement is record-based; null otherwise. # Migration guide v1 → v2 Source: https://docs.qobra.co/api_reference/v2/migration_guide What moves to v2, what stays on v1, and how to migrate your reporting integration ## Do you need to migrate? Only the **reporting** endpoints have a v2. v2 is not a new version of the whole API — it is a reporting-focused subset. If your integration does not call `GET /v1/reporting` or `GET /v1/reporting/{data_table_id}/records`, you have nothing to migrate. ## Support status of every endpoint | Endpoint | Status | Action | | - | - | - | | `GET /v1/reporting` | Deprecated | Migrate to [fetch statements records](/api_reference/v2/endpoints/data/fetch_statements) | | `GET /v1/reporting/{data_table_id}/records` | Deprecated | Migrate to [fetch reporting records](/api_reference/v2/endpoints/data/fetch_records) | | `GET /v1/users` | Maintained on v1 | Nothing to do | | `PUT /v1/users/bulk` | Maintained on v1 | Nothing to do | | `DELETE /v1/users/archive/bulk` | Maintained on v1 | Nothing to do | | `PUT /v1/users/groups/bulk` | Maintained on v1 | Nothing to do | | `PUT /v1/users/managers/bulk` | Maintained on v1 | Nothing to do | | `GET /v1/tables/{data_table_key}/records` | Maintained on v1 | Nothing to do | | `POST /v1/tables/{data_table_key}/records/bulk` | Maintained on v1 | Nothing to do | | `GET /v1/imports/{import_id}` | Maintained on v1 | Nothing to do | The deprecated reporting endpoints are still served today and no removal date is announced. As with any breaking change on the Qobra API, you would be notified by email two months in advance. The endpoints maintained on v1 have no end-of-life date — there is no v2 equivalent to move to. ## What changes on reporting Statements now take a `table_id`, discovered through `/v2/data-structures` `/v2/data-structures/{table_id}/fields` returns the exact keys, types and formats of your data `offset` is replaced by `start_id`, and the response hands you a ready-made `next_url` `last_modified_after` / `last_modified_before` let you pull only what moved ### Endpoint mapping | v1 | v2 | | - | - | | `GET /v1/reporting` | `GET /v2/reporting/{table_id}/statements` | | `GET /v1/reporting/{data_table_id}/records` | `GET /v2/reporting/{table_id}/records` | The v1 statements endpoint took no table in its path. In v2 both endpoints do, so start by listing your data structures to get the `table_id` to call. ### Parameter mapping | v1 | v2 | Notes | | - | - | - | | `offset` | `start_id` | Pass the ID of the last record received, or follow `meta.next_url` | | `limit` | `limit` | Unchanged in meaning, accepts 1 to 2000 | | — | `last_modified_after` | New. ISO 8601 datetime, for incremental syncs | | — | `last_modified_before` | New. ISO 8601 datetime | ### Response shape The envelope changes and the record keys are prefixed. ```json v1 theme={null} { "count": 4570, "next": "https://api.qobra.co/v1/reporting?offset=100&limit=100", "data": [ { "id": "6545110915808dbcc34531ef", "date": "2023-11-30", "payment": 8500.0, "payment_currency": "USD", "user": { "id": "5537a5b0...", "email": "john-doe@company.com" } } ] } ``` ```json v2 theme={null} { "data": [ { "standard.id": "65a1b2c3d4e5f6g7h8i9j0k1", "standard.date": "2024-01-01", "standard.total_commission": { "value": 8500.0, "currency": "USD" }, "standard.user": { "id": "507f191e...", "email": "sarah@company.com" } } ], "meta": { "next_start_id": "65a1b2c3d4e5f6g7h8i9j999", "has_more": true, "next_url": "https://api.qobra.co/v2/reporting/507f.../statements?start_id=65a1b2c3d4e5f6g7h8i9j999&limit=1000" } } ``` Three differences to handle in your parser: * **Envelope**: `count` / `next` / `data` becomes `data` / `meta`. Page forward with `meta.next_url` and stop on `meta.has_more`. * **Prefixed keys**: every field is namespaced by its origin — `standard.` for built-in fields, `custom.` for your custom metrics, `datatable.` for data table fields. * **Currency amounts**: the v1 pair `` + `_currency` becomes a single object `{ "value": …, "currency": … }`. Field keys are specific to your account. Do not guess them from this page — read them from [the fields endpoint](/api_reference/v2/endpoints/discovery/get_fields_schema), which returns every `api_key` with its type, format, currencies and enum values. ## Migrate in three steps Call `GET /v2/data-structures`. Each entry carries its `id`, its `type` (`statement_reporting` or `record_reporting`) and `links` pointing at the endpoints to call next. Call `GET /v2/data-structures/{table_id}/fields` and match each v1 key you consume to its v2 `api_key`. Store the returned `schema_hash` to detect later schema changes. Replace the `offset` loop with `meta.next_url`, and unwrap currency objects where your v1 code read a `_currency` sibling key. The [quickstart](/api_reference/v2/quickstart) runs these three calls end to end, with cURL, Python and JavaScript snippets you can copy. Your API key is unchanged — the same `X-API-Key` header authenticates both versions, and v1 and v2 can run side by side while you test. ## Checklist Call `/v2/data-structures` once to resolve the statement reporting `table_id`, then paginate with `meta.next_url` instead of incrementing `offset`. Those columns disappear. Read `value` and `currency` from the field's object instead, or flatten it back to two columns in your transformation layer. Nothing to migrate. Those endpoints stay on v1 and are maintained. Compare the `schema_hash` returned by the fields endpoint between two runs. This has no v1 equivalent. ## Next steps Run the three v2 calls end to end in 5 minutes Every v2 parameter and response field Need a hand? Reach us at [support@qobra.co](mailto:support@qobra.co) or through the in-app chat. # SSO Source: https://docs.qobra.co/authentication_documentation/sso/sso_documentation ## Introduction Single sign-on (SSO) is an authentication method that enables users to securely authenticate with multiple applications and websites by using just one set of credentials. Qobra’s SSO is implemented based on SAML (open standard used for authentication). This feature is pen-tested yearly by independent contractors. There are 3 parties involved in a SSO connection on Qobra: * The user who wishes to connect with his email address * Qobra, the application where the user wants to authenticate * The identity provider (IDP) which authenticates users and grand them access to the platform, such as `Okta`, `AzureDirectory`, `Keycloak`, etc … ## Setup You can manually setup your SSO in the Qobra platform (company settings - security). SSO setup dialog ### Step 1: Retrieve data from Qobra You’ll have first to retrieve the following information fields below, and input it in your IDP: * `Entity ID`: unique identifier that is used to identify a specific entity (here your Qobra account) in the SAML authentication. * `Redirect URL` or `Callback URL`: The callback URL represents the place where people will be redirected to after authenticating with their identity provider. ### Step 2: Input data in Qobra Then, you’ll have to fill in both field below to complete SAML setup: * `Domain name`: domain name used in the company's user email addresses. This field will default to your email’s domain name. * `Metadata URL`: URL of the SAML metadata, an XML document which contains information necessary for interaction with Identity Provider(IDP). This document contains essentials data to make the connection secure and successful (ex: URLs of endpoints, information about supported bindings, identifiers and public keys). The metadata available at the metadata URL should look something like this: ```xml theme={null} MIID7T...T7yNJg== MIID7TCC......QsT7yNJg== urn:oasis:names:tc:SAML:2.0:nameid-format:transient Administrator name@emailprovider.com ``` ### Step 3: Multiple domain names (optional) If your company currently has more then one domain name (because of a merger or rebranding typically), you can [contact the support](mailto:support@qobra.co) for them to fill in a second domain name for your company. You can’t do this action on your own for security purposes. ## Identity provider tutorials You'll find here step by step tutorials for our customers top 3 identity providers. If you'd like another identity provider to be present here, just ask us [here](mailto:tanguy@qobra.co). 1. Go to your admin dashboard, in the Applications tab. 2. Click on the button **“Create a new app integration”** and select SAML 2.0 New app integration 3. Fill the basic info of the app (App name: Qobra, logo, etc.) Fill in basic info 4. Fill the SAML settings as shown below. There is no specificity in the advanced settings for this setup but your organization could have some specific constrains to be filled. Please contact your organization IT for more information. Fill in SAML settings 5. You can finish the setup by providing the following settings. Finish the setup 6. Congratulations you now have access to the metadata URL. Access the metadata URL 1. Go to the `Enterprise Applications` tab and click the `New Application` button. Enterprise Applications Tab 2. Select the `Microsoft Entra SAML Toolkit` application and put `Qobra` in the name field. Saml Toolkit Application Gallery You can now click on the `Create` button 3. Once on your new application, go to the `Single sign-on` tab and click on `SAML` Single sign-on tab 4. You can now click on the `Edit` button to start putting the SAML data Fill in SAML data 5. Go to your SSO settings at [https://app.qobra.co/parameters/security](https://app.qobra.co/parameters/security) Qobra SSO data 6. Thanks to the information gathered on your SSO settings you can now fill those 3 fields: * Entity ID * Reply URL (`Redirect URL` in Qobra) * Sign on URL (value: [https://app.qobra.com/auth](https://app.qobra.com/auth)) Fill SAML data After filling those fields, you can now click on the `Save` button. 5. Copy the `App Federation Metadata Url` in the `SAML Certificates` section and fill it in Qobra SSO config (`Metadata URL` field) App Federation Metadata Url You should also fill the domain name of your email in the `Domain name` field Update Qobra SAML config 1. Go to administration UI to add a client. Add a client 2. Go to the next step and complete login settings as shown below. Complete login settings 3. Once the app is created, you MUST update theses parameters on client settings: * Sign Assertion: ON Fill in 'Sign assertion' option * Name ID Format: Email Fill in 'Name ID Format' option * Signing key config: OFF Fill in 'Signing key config' option 4. Now you can access the metadata URL, as shown below Access the metadata URL ## Troubleshooting ### Can’t find the Metadata URL * If you use Okta, KeyCloak or AzureDirectory, refer to the documentation about those specific identity providers. * If your identity provider only allow clients to download your metadata, you’ll have to serve them through an URL (S3, cloud storage, static server, etc …). Qobra does not allow the upload of SAML metadata in the platform. ### Metadata URL is rejected The host of your Metadata URL may point to an internal destination. It happened when you self-host your identity provider on your internal network. As a security measure and because we cannot access this internal host, we block the request. You must serve your metadata through a public URL. ### Can’t authenticate after registering your SSO Check your Metadata URL by visiting it directly on your browser: * The page should not return an error * The page should return an XML file * The assertions should be signed in your XML file ### Issues with setting home realm discovery As a security measure, an admin is only able to set a home realm discovery matching the domain name of his email address. To change it to another client domain, you should ask the support for help. # Attio Source: https://docs.qobra.co/integration_documentation/crms/attio ## Introduction Qobra connects to Attio through an application, which needs to be installed via an Oauth check. The integration is used to synchronize your Attio data, both **Objects** (standard and custom) and **Lists**, with their fields and their content. This integration is readonly and does not write anything in your Attio workspace. ## Access ### Oauth authentication & scopes To connect your Attio to Qobra, you'll need to go through an Oauth flow, granting us access to several read-only scopes: * `record_permission:read` (Read the content of your records) * `object_configuration:read` (Read objects and their attributes) * `list_configuration:read` (Read lists and their attributes) * `list_entry:read` (Read list entries) * `user_management:read` (Read your workspace members) You'll find more information about Attio scopes [here](https://docs.attio.com/rest-api/guides/authentication). ## Synchronization ### Refresh Frequency Qobra keeps your Attio data up to date through regular synchronizations: * **Scheduled Full Refresh**: on a recurring schedule, we perform a complete sync of all your Attio data * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings ### Total synchronization This integrations does total synchronization, meaning the whole document will be synced at every refresh. Unlike CRMs such as Salesforce or Hubspot, Attio does not expose a record-level last modified date. As a result, Qobra performs a complete refresh of your data on every synchronization rather than an incremental one. ### Supported tables The integration relies on Attio's schema discovery, so it supports all the data available in your workspace: * **Objects**: all standard objects (such as `People`, `Companies`, `Deals`) as well as any custom object defined in your workspace, with their standard and custom attributes. * **Lists**: each list is synchronized as its own table, including its list attributes and its parent record. User attribution in Qobra is based on your Attio **workspace members**, not on the standard `Users` object (which represents the end-users of your own product). ### Supported types You’ll find below the fields types supported by the Qobra integration, and their mapping to Qobra types. | **Attio type** | **Qobra type** | | - | - | | `text` | `string` | | `personal-name` | `string` | | `email-address` | `string` | | `phone-number` | `string` | | `domain` | `string` | | `location` | `string` | | `number` | `number` | | `rating` | `number` | | `currency` | `amount` | | `checkbox` | `bool` | | `date` | `date` | | `timestamp` | `date` | | `select` | `picklist` or `multipicklist` | | `status` | `picklist` or `multipicklist` | | `record-reference` | `object` | | `actor-reference` | `user` | ### Multi-currency amounts Attio's built-in `currency` attribute only stores a single ISO currency code per workspace, which is not enough when your records mix currencies. To work around this, Qobra lets you map any `number` attribute to Qobra's `amount` type and pick a **separate attribute that carries the ISO currency code** for each record. Use this when you need to import amounts (revenue, ACV, commission base…) whose currency varies from one record to the next. #### Configure the currency code source 1. In Qobra, open the field mapping for a `number` attribute imported as an `amount`. 2. Under **Currency code**, either keep **Company currency** (the default, applied to every record) or pick an Attio attribute to read the code from for each record. 3. Save. The new mapping is applied on the **next import** — existing rows keep their previously imported currency until then. 4. Trigger a manual refresh from the integration settings if you want the change to take effect immediately. #### Supported source attributes The currency code can come from any of these Attio attribute types on the same table: | **Attio attribute type** | **How the code is read** | | - | - | | `text` | The raw text value. | | `select` (Picklist) | The **option label**, which must be the code. | | `status` (Picklist) | The **status label**, which must be the code. | Multi-select `select` and `status` attributes are **not** eligible — a multi-valued attribute has no single code to read. #### ISO code requirement and fallback Values (or option labels) must be a valid ISO 4217 code such as `EUR`, `USD`, or `GBP`. If the value is empty, unrecognized, or non-string, Qobra silently falls back to your **company currency**. Casing and surrounding whitespace are normalized, so `usd`, `USD` and `USD` are treated the same. For `select` and `status` sources, name the options directly with the ISO code (for example, an option labeled `EUR` rather than `Euro`), otherwise the record will fall back to the company currency. #### Attio Lists For a **List** table, the currency code attribute must live **on the List itself**, not on the parent Object. Only attributes exposed on the same List entry can be resolved during import. [Contact Qobra support](mailto:support@qobra.co) for more information. # Hubspot Source: https://docs.qobra.co/integration_documentation/crms/hubspot ## Introduction Qobra connects to Hubspot through an application, which needs to be installed via an Oauth check. The integration is used to synchronize tables from Hubspot, with their fields and their content. The application supports both standard and custom objects from the CRM. This integration is readonly and does not write anything in your Hubspot. ## Access ### Oauth authentication & scopes To connect your Hubspot to Qobra, you’ll need to go through an Oauth flow, granting us access to several scopes: * `crm.objects.companies.read` * `crm.schemas.companies.read` * `crm.objects.contacts.read` * `crm.schemas.contacts.read` * `crm.objects.deals.read` * `crm.schemas.deals.read` * `crm.objects.owners.read` * `crm.objects.custom.read` (optional) * `crm.schemas.custom.read` (optional) * `crm.objects.goals.read` (optional) * `e-commerce` (optional) * `crm.objects.leads.read` (optional) You’ll find more information about Hubspot application scopes [here](https://developers.hubspot.com/docs/api/scopes). ## Synchronization ### Refresh Frequency To ensure your Hubspot data is always up to date in Qobra, we maintain the following synchronization schedule: * **Daily Full Refresh**: Every night, we perform a complete sync of all your Hubspot data * **Bi-hourly Updates**: Every 2 hours throughout the day, we sync any recent changes * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings ### Incremental synchronization This integration is an incremental synchronization. This means that all imports are based on each record last modified date, and that we only refresh what has been modified in the system since the previous import. When an import is launched, we thus refresh all records that have been modified since the start of the last successful import. Once we have refresh range, synchronization work is split and parallelized in smaller tasks for performance enhancement. The synchronization tasks are split according to the last modified date. It is important to note that Qobra admin can manually trigger complete synchronization in the application. For Hubspot, we base this synchronization on the `lastModifiedDate` field, present in each table. ### Supported tables You’ll find here all tables supported by the Qobra integration. | **Tables** | **Name field** | **Currency field** | **Jointures** | | - | - | - | - | | `Deals` | `dealname` | `deal_currency_code` | `Companies`, `Contacts` | | `Companies` | `name` | | | | `Line Items` | `name` | `hs_line_item_currency_code` | `Deals` | | `Meetings` | `hs_meeting_title` | | `Companies`, `Deals` | | `Calls` | `hs_call_title` | | | | `Emails` | `hs_email_subject` | | | | `Tasks` | `hs_task_subject` | | | | `Contacts` | `email` | | `Companies`, `Meetings` | | `Leads` (optional) | `hs_lead_name` | | `Companies`, `Contacts` | | `Goals` (optional) | `hs_goal_name` | | | | `Custom tables` (optional) | `primaryDisplayProperty` | | | ### Supported types You’ll find below the fields types supported by the Qobra integration, and their mapping to Qobra types. | **Hubspot type** | **Qobra type** | | - | - | | `number` | `amount`, `number` | | `user` | `user` | | `string` | `string` | | `enumeration` | `picklist`, `user` | | `bool` | `bool` | | `datetime` | `date` | | `date` | `date` | ### Custom querying With this integration, you can apply a custom query to your table synchronization, to avoid synchronizing too much information, consequently making synchronization quicker. Custom queries are based on the hubspot API where conditions. For instance, you could add a custom query like below to filter your Deal table on stage field. ```json theme={null} { "propertyName": "dealstage", "operator": "IN", "values": ["appointmentscheduled", "contractsent", "qualifiedtobuy"] } ``` You'll find more information in Hubspot's documentation [here](https://developers.hubspot.com/docs/guides/api/crm/search#filter-search-results). [Contact Qobra support](mailto:support@qobra.co) for more information. # Microsoft Dynamics Source: https://docs.qobra.co/integration_documentation/crms/microsoft_dynamics ## Introduction Qobra connects to Microsoft Dynamics through an application, which needs to be installed via an Oauth check. The integration is used to synchronize tables from Microsoft Dynamics, with their fields and their content. This integration is readonly and does not write anything in your Microsoft Dynamics. ## Access ### Oauth authentication & scopes To connect your Microsoft Dynamics to Qobra, you’ll need: 1. To fill in the domain name of your Microsoft Dynamics instance. It should look something like this: `https://org123.crm12.dynamics.com`. 2. To go through an Oauth flow, granting us access to the `.default` scope. You’ll find more information about this scope [here](https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc#the-default-scope). ## Synchronization ### Refresh Frequency We keep your Microsoft Dynamics data synchronized with Qobra using the following schedule: * **Daily Full Refresh**: Every night, we perform a complete sync of all your Dynamics data * **Bi-hourly Updates**: Every 2 hours throughout the day, we sync any recent changes * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings ### Incremental synchronization This integration is an incremental synchronization. This means that all imports are based on each record last modified date, and that we only refresh what has been modified in the system since the previous import. When an import is launched, we thus refresh all records that have been modified since the start of the last successful import. Once we have refresh range, synchronization work is split and parallelized in smaller tasks for performance enhancement. The synchronization tasks are split according to the last modified date. It is important to note that Qobra admin can manually trigger complete synchronization in the application. ### Supported types You’ll find below the fields types supported by the Qobra integration, and their mapping to Qobra types. | **Microsoft Dynamics type** | **Qobra type** | | - | - | | `Decimal` | `number` | | `Integer` | `number` | | `Money` | `amount` | | `String` | `string` | | `Memo` | `string` | | `Picklist` | `picklist` | | `Multipicklist` | `multipicklist` | | `Boolean` | `bool` | | `DateTime` | `date` | # Odoo Source: https://docs.qobra.co/integration_documentation/crms/odoo ## Introduction Qobra connects to Odoo thanks to a user API key. The integration is used to synchronize tables, with their fields and their content. This integration is readonly and does not write anything in your Odoo account. ## Setup 1. Create your API key, as documented [here](https://www.odoo.com/documentation/18.0/developer/reference/external_api.html#api-keys). 2. Fill in information in Qobra: * `Username`: the email of the person who created the API key * `Password / API Key`: API key (it is not advised connecting directly with password) * `Instance URL`: Your Odoo instance URL * `Database`: Odoo Database Odoo setup in Qobra ## Synchronization ### Refresh Frequency For Odoo integrations, we maintain a different synchronization schedule than other CRMs: * **Bi-hourly Full Refresh**: Every 2 hours throughout the day, we perform a complete sync of your Odoo data * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings Note: Unlike other CRM integrations, Odoo uses full refreshes every 2 hours instead of combining daily full refreshes with incremental updates. ### Supported types You’ll find below the fields types supported by the Qobra integration, and their mapping to Qobra types. | **Odoo type** | **Qobra type** | | - | - | | `float` | `number`, `amount` | | `integer` | `number`, `user` | | `monetary` | `amount` | | `text` | `string` | | `char` | `string`, `user` | | `boolean` | `bool` | | `selection` | `picklist` | | `date` | `date` | | `datetime` | `date` | | `user` | `user` | # Pipedrive Source: https://docs.qobra.co/integration_documentation/crms/pipedrive ## Introduction Qobra connects to Pipedrive through an application, which needs to be installed via an Oauth check. The integration is used to synchronize tables from Pipedrive, with their fields and their content. This integration is readonly and does not write anything in your Pipedrive. ## Access ### Oauth authentication & scopes To connect your Pipedrive to Qobra, you’ll need to go through an Oauth flow, granting us access to several scopes: * `base` (Access to basic information) * `deals:read` (Deals: Read only) * `mail:read` (Mail: Read only) * `activities:read` (Activities: Read only) * `contacts:read` (Contacts: Read only) * `products:read` (Products: Read only) * `users:read` (Read users data) * `recents:read` (See recent account activity) * `search:read` (Search for all data) * `leads:read` (Leads: Read only) * `goals:read` (Goals: Read only) You’ll find more information about Pipedrive application scopes [here](https://pipedrive.readme.io/docs/marketplace-scopes-and-permissions-explanations). ## Synchronization ### Refresh Frequency Your Pipedrive data is kept in sync with Qobra through our automated refresh schedule: * **Daily Full Refresh**: Every night, we perform a complete sync of all your Pipedrive data * **Bi-hourly Updates**: Every 2 hours throughout the day, we sync any recent changes * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings ### Incremental synchronization This integration is an incremental synchronization. This means that all imports are based on each record last modified date, and that we only refresh what has been modified in the system since the previous import. When an import is launched, we thus refresh all records that have been modified since the start of the last successful import. Once we have refresh range, synchronization work is split and parallelized in smaller tasks for performance enhancement. The synchronization tasks are split according to the last modified date. It is important to note that Qobra admin can manually trigger complete synchronization in the application. ### Supported tables You’ll find here all tables supported by the Pipedrive integration: | **Pipedrive table** | **Name field** | | - | - | | `Deals` | `title` | | `Activities` | `subject` | | `Organizations` | `name` | | `Persons` | `name` | | `Products` | `name` | ### Supported types You’ll find below the fields types supported by the Qobra integration, and their mapping to Qobra types. | **Pipedrive type** | **Qobra type** | | - | - | | `double` | `number` | | `int` | `number` | | `monetary` | `amount` | | `varchar` | `string` | | `enum` | `picklist` | | `status` | `picklist` | | `set` | `multi-picklist` | | `date` | `date` | | `user` | `user` | # Salesforce Source: https://docs.qobra.co/integration_documentation/crms/salesforce ## Introduction Qobra connects to Salesforce through a connected app, which needs to be installed via an Oauth check. The integration is used to synchronize all objects, their fields and their content. The integration supports standard or custom objects, and standard or custom fields in those objects. This integration is readonly and does not write anything in your Salesforce. ## Access ### Oauth authentication & scopes To connect your Salesforce to Qobra, you’ll need to go through an Oauth flow, granting us access to two Salesforce scopes: * `Perform requests at any time` (refresh\_token, offline\_access) * `Manage user data via APIs` (api) You’ll find more information about Salesforce connected app scopes [here](https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_tokens_scopes.htm\&type=5). ### OAuth login – common pitfalls When logging into Salesforce, please pay attention to the following points: * Make sure you are logging into the correct Salesforce environment (Production or Sandbox). * If you are logging in as an API-only user, ensure this user is fully logged out of Salesforce before starting the OAuth connection. * Be careful when using Salesforce custom domains and verify you are authenticating through the correct domain. If you encounter the following error during the OAuth flow `OAUTH_APPROVAL_ERROR_GENERIC`, and the callback URL contains `error_description=app+must+be+installed+into+org`, follow these steps: * Go to *Setup* → *Connected Apps OAuth Usage* * Find the Qobra connected app * If the status shows *Install* (or *Approve*), click it * Retry the OAuth connection ### Access restriction The Qobra integrations has access to all the data the user who connected has access to. A common practice for our customers is to create an `API only` user in Salesforce to give Qobra’s access through this user (unlimited, enterprise, and performance Salesforce edition orgs get five free `API only` licenses). You can restrict this user’s access to Salesforce data, and then give us access through the Oauth flow to restrict Qobra’s access to a part of your Salesforce. Follow [this tutorial](https://admin.salesforce.com/blog/2023/best-practices-for-configuring-your-integration-user) to implement Salesforce's best security practices. ## Synchronization ### Refresh Frequency Qobra keeps your Salesforce data up to date through regular synchronizations: * **Daily Full Refresh**: Every night, we perform a complete sync of all your Salesforce data * **Bi-hourly Updates**: Every 2 hours throughout the day, we sync any recent changes * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings For Salesforce, we base this synchronization on the `LastModifiedDate` field, present in each table. | **Salesforce type** | **Qobra type** | | - | - | | `int` | `number` | | `double` | `number` | | `percent` | `percentage` | | `currency` | `amount` | | `picklist` | `picklist` | | `multipicklist` | `multipicklist` | | `textarea` | `string` | | `string` | `string` | | `combobox` | `string` | | `phone` | `string` | | `email` | `string`, `user` | | `boolean` | `bool` | | `date` | `date` | | `datetime` | `date` | | `reference` | `object` | | `reference` > `user` | `user` | When you import a Salesforce `email` field, you can choose to map it either as a `string` or as a `user`. When mapped as `user`, Qobra matches the email value against the email addresses of the users of your Qobra company: * If a user matches, the record is linked to that user, and the cell displays the user's avatar and name. * If no user matches, the email value is kept as-is in the record, and the cell displays *Not imported*. Hovering over the cell shows a tooltip indicating that this email does not match any user of your company. The match is exact and case-sensitive. Qobra integration supports multi-currency synchronization, based on the `CurrencyIsoCode` field, present in each Salesforce object. Custom queries are based on SOQL where conditions. For instance, you could add as a custom query `StageName = 'Closed Won’` to filter your Opportunity object. ## Polymorphic References Salesforce uses polymorphic references (also known as "polymorphic lookup fields" or "WhoId/WhatId fields") in certain objects like Events and Tasks. These special reference fields can point to multiple types of entities. For example: * The `WhoId` field can reference Leads, Contacts, or Users * The `WhatId` field can reference Accounts, Opportunities, Cases, and other custom objects Unfortunately, Qobra does not currently support polymorphic references due to their complex nature. When synchronizing objects that contain these fields, the references will not be properly resolved. ### Workarounds To handle this limitation, here are some suggested workarounds: 1. **Create Separate Fields**: Instead of using the polymorphic fields, you can create separate lookup fields for each type of entity you need to reference. For example: * `Account__c` * `Opportunity__c` * `Lead__c` Then use Process Builder or Flow to automatically populate these fields based on the WhatId/WhoId values. 2. **Use Record Types**: Create different record types for your Events/Tasks based on the type of entity they're related to. This allows you to have different page layouts and field sets for each type of relationship. 3. **Custom Junction Objects**: For complex relationships, consider creating custom junction objects that explicitly define the relationships between your records. [Contact Qobra support](mailto:support@qobra.co) for more information. # Zoho Source: https://docs.qobra.co/integration_documentation/crms/zoho ## Introduction Qobra connects to Zoho through an application, which needs to be installed via an Oauth check. The integration is used to synchronize tables from Zoho, with their fields and their content. This integration is readonly and does not write anything in your Zoho. ## Access ### Oauth authentication & scopes To connect your Pipedrive to Qobra, you’ll need to go through an Oauth flow, granting us access to several scopes: * `ZohoCRM.users.READ` (Read user information) * `ZohoCRM.modules.READ` (Read all modules information) * `ZohoCRM.settings.fields.READ` (Read settings) You’ll find more information about Zoho application scopes [here](https://www.zoho.com/crm/developer/docs/api/v6/scopes.html). ## Synchronization ### Refresh Frequency To ensure your Zoho CRM data stays current in Qobra, we follow this synchronization schedule: * **Daily Full Refresh**: Every night, we perform a complete sync of all your Zoho data * **Bi-hourly Updates**: Every 2 hours throughout the day, we sync any recent changes * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings ### Incremental synchronization This integration is an incremental synchronization. This means that all imports are based on each record last modified date, and that we only refresh what has been modified in the system since the previous import. When an import is launched, we thus refresh all records that have been modified since the start of the last successful import. Once we have refresh range, synchronization work is split and parallelized in smaller tasks for performance enhancement. The synchronization tasks are split according to the last modified date. It is important to note that Qobra admin can manually trigger complete synchronization in the application. ### Supported tables You’ll find here all tables supported by the Zoho integration: | **Zoho table** | **Name field** | | - | - | | `Deals` | `Deal_Name` | | `Leads` | `Full_Name` | | `Invoices` | `Invoice_Number` | | `Accounts` | `Account_Name` | | `Contacts` | `Full_Name` | | `Events` | `Event_Title` | ### Supported types You’ll find below the fields types supported by the Qobra integration, and their mapping to Qobra types. | **Zoho type** | **Qobra type** | | - | - | | `double` | `number` | | `integer` | `number` | | `currency` | `amount` | | `text` | `string` | | `textarea` | `string` | | `date` | `date` | | `datetime` | `date` | | `picklist` | `picklist` | | `userlookup` | `user` | | `ownerlookup` | `user` | | `formula` | `string`, `number`, `amount`, `percentage`, `date` | # Bigquery Source: https://docs.qobra.co/integration_documentation/databases/bigquery ## Introduction Qobra connects to Bigquery through google service accounts. This means that Qobra will only be able to access the data available to the service account. If you want to limit access to specific tables, you should restrict the service account's permissions accordingly. Although a service account is tied to a project, it does not automatically have access to all the data within that project. You can control its access by configuring IAM roles and permissions to limit it to specific datasets and tables. **Note**: Qobra will still be able to view the names of all tables and routines within a dataset, even if it does not have permission to access their contents. The integration is used to synchronize exposed tables, their columns and their content. This integration is readonly and does not write anything in your Bigquery database. ## Setup 1. Create a service account from your google account, with the following permissions: * `bigquery.jobs.create` * `bigquery.tables.get` * `bigquery.tables.list` * `bigquery.routines.get` * `bigquery.routines.list` * `bigquery.tables.getData` (optional) You'll find more information about creating service account [here](https://cloud.google.com/iam/docs/service-accounts-create). In the end, your service account should look something like this: ```json theme={null} { "type": "service_account", "project_id": "project-id", "private_key_id": "key-id", "private_key": " - - -BEGIN PRIVATE KEY - - -\nprivate-key\n - - -END PRIVATE KEY - - -\n", "client_email": "service-account-email", "client_id": "client-id", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://accounts.google.com/o/oauth2/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/service-account-email" } ``` 2. You’ll then be able to fill in connection modal in app: * `Service account`: Service account * `Dataset`: Bigquery dataset Bigquery setup in Qobra 3. (optional) If the data feeding your table is coming from a Google Spreadsheet, you'll need the `bigquery.tables.getData` permission. In the source spreadsheet, you'll also need to give a read access to the service account's email address. ## Synchronization ### Table requirements To synchronize a file to Qobra, it must meet requirements: * the file contains a **unique identifier** column * the file contains a **name** column * the user values contained in the file must be emails by default, but it can be changed in app at table creation ### Total synchronization This integrations does total synchronization, meaning the whole document will be synced at every refresh. ### Refresh Schedule By default, we perform one Full Refresh per day to keep your data up to date. However, you can customize both the frequency and timing of these refreshes to better suit your needs: * **Default Schedule**: one Full Refresh per day * **Customizable Options**: * Adjust the number of daily refreshes * Set specific hours for the refreshes to occur * Configure the schedule through your integration settings * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings Refresh schedule setup in Qobra ### Supported types You’ll find below the fields types supported by the Qobra integration, and their mapping to Qobra types. | **Bigquery type** | **Qobra type** | | - | - | | `STRING` | `string`, `picklist`, `multipicklist`, `user` | | `INT64` | `number`, `amount`, `percentage`, `user`, `picklist`, `multipicklist` | | `FLOAT64` | `number`, `amount`, `percentage` | | `NUMERIC` | `number`, `amount`, `percentage` | | `TIMESTAMP` | `date` | | `DATE` | `date` | | `DATETIME` | `date` | | `BOOL` | `bool` | ### Custom querying With this integration, you can apply a custom query to your table synchronization, to avoid synchronizing too much information, consequently making synchronization quicker. Custom queries are based on SQL where conditions. For instance, you could add as a custom query `StageName = 'Closed Won’` to filter your Opportunity object. Contact support for more information. # PostgreSQL Source: https://docs.qobra.co/integration_documentation/databases/postgresql ## Introduction Qobra can connect to PostgreSQL by two means: * username / password credentials. * service account (only available for Google Cloud Platform) The integration is used to synchronize exposed tables, their columns and their content. This integration is readonly and does not write anything in your PostgreSQL database. ## Setup via SQL user 1. Create an account to setup the Qobra connection, ensuring the principle of least privilege. This means the account needs to have read only access, on table that need to be synchronized with Qobra. You’ll find Postgre’s documentation regarding User, role, & privilege commands [here](https://www.postgresql.org/docs/8.0/user-manag.html). Once your account is created, make sure you keep its username and password for later steps. 2. Whitelist Qobra’s IP: `13.39.2.144` 3. You’ll then be able to fill in connection modal in app: * `Username`: PostgreSQL account username * `Password`: PostgreSQL account password * `Host`: PostgreSQL account’s unique URL * `Port`: by default 5432 * `Database`: PostgresSQL database PostgreSQL setup via SQL user ## Setup via service account 1. Create a service account from your google account, it should look something like this: ```json theme={null} { "type": "service_account", "project_id": "project-id", "private_key_id": "key-id", "private_key": " - - -BEGIN PRIVATE KEY - - -\nprivate-key\n - - -END PRIVATE KEY - - -\n", "client_email": "service-account-email", "client_id": "client-id", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://accounts.google.com/o/oauth2/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/service-account-email" } ``` 2. You’ll then be able to fill in connection modal in app: * `Username`: PostgreSQL account username * `Password`: PostgreSQL account password * `Service account`: PostgreSQL account’s unique URL * `Instance`: PostgresSQL instance * `Database`: PostgresSQL database PostgreSQL setup via service account ### Table requirements To synchronize a file to Qobra, it must meet requirements: * the file contains a **unique identifier** column * the file contains a **name** column * the user values contained in the file must be emails by default, but it can be changed in app at table creation ### Total synchronization This integrations does total synchronization, meaning the whole document will be synced at every refresh. ### Refresh Schedule By default, we perform one Full Refresh per day to keep your data up to date. However, you can customize both the frequency and timing of these refreshes to better suit your needs: * **Default Schedule**: one Full Refresh per day * **Customizable Options**: * Adjust the number of daily refreshes * Set specific hours for the refreshes to occur * Configure the schedule through your integration settings * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings Refresh schedule setup in Qobra ### Supported types You’ll find below the fields types supported by the Qobra integration, and their mapping to Qobra types. | **Postgres type** | **Qobra type** | | - | - | | `TEXT` | `string`, `picklist`, `multipicklist`, `user` | | `CHARACTER` | `string`, `picklist`, `multipicklist`, `user` | | `CHARACTER VARYING` | `string`, `picklist`, `multipicklist`, `user` | | `DATE` | `date` | | `TIMESTAMP WITHOUT TIME ZONE` | `date` | | `TIMESTAMP WITH TIME ZONE` | `date` | | `INTEGER` | `number`, `amount`, `percentage`, `user` | | `SMALLINT` | `number`, `amount`, `percentage`, `user` | | `BIGINT` | `number`, `amount`, `percentage`, `user` | | `DOUBLE PRECISION` | `number`, `amount`, `percentage` | | `NUMERIC` | `number`, `amount`, `percentage` | | `FLOAT` | `number`, `amount`, `percentage` | | `REAL` | `number`, `amount`, `percentage` | | `MONEY` | `amount` | | `BOOLEAN` | `bool` | | `BIT` | `string` | | `BIT VARYING` | `string` | ### Custom querying With this integration, you can apply a custom query to your table synchronization, to avoid synchronizing too much information, consequently making synchronization quicker. Custom queries are based on SQL where conditions. For instance, you could add as a custom query `StageName = 'Closed Won’` to filter your Opportunity object. Contact support for more information. # snowflake Source: https://docs.qobra.co/integration_documentation/databases/snowflake The integration is used to synchronize exposed tables, their columns, and their content. It is read-only and does not write anything to your Snowflake database. ## Setup 1. Create a dedicated account to set up the Qobra connection, following the principle of least privilege. This means the account should have read-only access to the tables that need to be synchronized with Qobra. You’ll find Snowflake’s documentation regarding *User, role, and privilege commands* [here](https://docs.snowflake.com/en/sql-reference/commands-user-role). 2. Whitelist Qobra’s IP address: `13.39.2.144` (infra EU) or `18.206.233.133` (infra US). 3. Configure key-pair authentication to enable the Snowflake integration within the Qobra application. We recommend following the official [Snowflake documentation](https://docs.snowflake.com/en/user-guide/key-pair-auth#configuring-key-pair-authentication) to correctly generate and configure the key pair required to complete the connection. 4. You’ll then be able to fill in the connection modal in app: * `Username`: Snowflake account username * `Host`: Snowflake account’s unique URL * `Port`: by default `443` for encrypted (TLS/SSL) connections * `Database`: Snowflake database * `SSH private key`: The private SSH key generated at step 3 Snowflake setup in Qobra ## Synchronization | **Snowflake type** | **Qobra type** | | - | - | | `TEXT` | `string`, `picklist`, `multipicklist`, `user` | | `NUMBER` | `number`, `amount`, `percentage`, `user` | | `FLOAT` | `number`, `amount`, `percentage` | | `DATE` | `date` | | `TIMESTAMP_TZ` | `date` | Custom queries are based on SQL where conditions. For instance, you could add as a custom query `StageName = 'Closed Won’` to filter your Opportunity object. Contact support for more information. # NetSuite Source: https://docs.qobra.co/integration_documentation/erp/netsuite ## Introduction Qobra connects to NetSuite with Token-Based Authentication (TBA), through the SuiteTalk REST web services. The integration is used to synchronize your NetSuite records into Qobra data tables, and optionally to import your users from NetSuite employees. It is read-only and does not write anything to your NetSuite account. ## Setup The setup is done by a NetSuite administrator. ### 1. Enable the features Go to **Setup > Company > Enable Features**: * **SuiteCloud** subtab: check **REST Web Services** and **Token-Based Authentication**. * **Analytics** subtab: check **SuiteAnalytics Workbook**. ### 2. Create a role for Qobra Go to **Setup > Users/Roles > Manage Roles > New** and create a role dedicated to Qobra, with the following permissions: | **Subtab** | **Permission** | **Level** | | - | - | - | | Setup | Log in using Access Tokens | Full | | Setup | REST Web Services | Full | | Reports | SuiteAnalytics Workbook | Edit | | Transactions, Lists | Each record you want to import in Qobra | View | For instance, add `Sales Order`, `Invoice` or `Customer` with the View level if you want to import these records. Do not use the Administrator role: Oracle advises against it for web services integrations. See NetSuite's documentation: [Prerequisites and Setup for REST Web Services](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/article_5085602973.html). #### User import permissions These permissions are optional: add them to the role only if you want to import your users from NetSuite employees. | **Subtab** | **Permission** | **Level** | | - | - | - | | Lists | Employees | View | | Lists | Employee Record | View | | Lists | Perform Search | View | Without them, Qobra cannot read your employees and the user import from NetSuite stays empty. See NetSuite's documentation: [Employee record in REST web services](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_161886175135.html). ### 3. Create a user for Qobra Create a user dedicated to Qobra (**Lists > Employees > New**). On its **Access** subtab, give it access to NetSuite and assign it the role created at step 2. ### 4. Create an integration record Go to **Setup > Integration > Manage Integrations > New**, name it `Qobra`, and under **Authentication** check **Token-Based Authentication**. Save: the **Consumer Key** and **Consumer Secret** are displayed. See NetSuite's documentation: [Create Integration Records for Applications to Use TBA](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/bridgehead_4249032125.html). ### 5. Create an access token Go to **Setup > Users/Roles > Access Tokens > New**, and select the `Qobra` application, the user created at step 3 and the role created at step 2. Save: the **Token ID** and **Token Secret** are displayed. See NetSuite's documentation: [Access Token Management – Create and Assign a TBA Token](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/bridgehead_4254081947.html). NetSuite displays the consumer and token secrets only once, at creation. Copy them right away: if you lose them, you will have to generate new ones. ### 6. Fill in the connection modal in Qobra The base URL and the account ID are both on **Setup > Company > Company Information**. | **Field** | **Where to find it** | | - | - | | `Base URL` | **Company URLs** subtab, **SuiteTalk** URL | | `Account ID` | **Account ID** field | | `Consumer key` | Integration record, step 4 | | `Consumer secret` | Integration record, step 4 | | `Token ID` | Access token, step 5 | | `Secret token` | Access token, step 5 | The `Base URL` follows the format `https://[ACCOUNT-ID].suitetalk.api.netsuite.com`. See NetSuite's documentation: [URLs for Account-Specific Domains](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_1498251763.html). For a sandbox, the account ID is written differently in the two fields: * `Base URL`: lowercase with a hyphen, e.g. `https://1234567-sb1.suitetalk.api.netsuite.com` * `Account ID`: uppercase with an underscore, e.g. `1234567_SB1` The `Base URL` is never the URL of the NetSuite interface (`app.netsuite.com`). [Contact Qobra support](mailto:support@qobra.co) for more information. # S3 Source: https://docs.qobra.co/integration_documentation/files/s3 ## Introduction Qobra can connect to your AWS account and synchronize `csv` files from an S3 bucket. This integration is readonly and will not write anything in your S3 buckets. ## Access & setup 1. Create the S3 bucket that will hold your synchronized `csv` files. 2. Create an AWS policy with the least access needed for the integration to work: * `s3:ListBucket`: lists S3 buckets (among restricted resources) * `s3:GetObject`: retrieves documents from S3 buckets (among restricted resources) The policy should look something like this: ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::qobra-integration-bucket", "arn:aws:s3:::qobra-integration-bucket/*" ] } ] } ``` Attach the policy to the IAM user or role to give us access to the S3 bucket. *If you are using resource based policy, make sure the bucket policy is updated accordingly.* 3. Fill in your bucket information in the application: S3 setup in Qobra ## Synchronization ### CSV file requirements To synchronize a file to Qobra, it must meet requirements: * the file has a `csv` format, with `;` separator * the file does not change name * the file contains a **unique identifier** column * the file contains a **name** column * the date values contained in the file must follow this format: `YYYY-MM-DD` * the user values contained in the file must be emails by default, but it can be changed in app at table creation ### Total synchronization This integrations does total synchronization, meaning the whole document will be synced at every refresh. ### Refresh Schedule By default, we perform one Full Refresh per day to keep your data up to date. However, you can customize both the frequency and timing of these refreshes to better suit your needs: * **Default Schedule**: one Full Refresh per day * **Customizable Options**: * Adjust the number of daily refreshes * Set specific hours for the refreshes to occur * Configure the schedule through your integration settings * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings Refresh schedule setup in Qobra # SFTP Source: https://docs.qobra.co/integration_documentation/files/sftp ## Introduction Qobra can connect and synchronize `csv` files thanks to the SFTP protocol. ⚠️ You are pushing files on Qobra's SFTP server. Only one SFTP is allowed for a company. ## SSH key generation You can write data in our SFTP server using SSH Key pair as authentication. You authenticate using your private key on your machine and we use your public key to verify the sender. When you generate an SSH key, you can add a passphrase to further secure the key. If so, you must enter the passphrase each time you use the key. If you don't already have an SSH key, you must generate a new SSH key to use for authentication. You can generate a new SSH key on your machine. After you generate the key, keep the public part of the key to provide it to Qobra later on. Key type allowed are `rsa`, `ed25519`, `sha2` and `ecdsa`. 1. Open Terminal. 2. Paste the text below, replacing the email used in the example with your qobra user email address. ```bash theme={null} ssh-keygen -t ed25519 -C "your_email@example.com" ``` or ```bash theme={null} ssh-keygen -t rsa -b 4096 -C "your_email@example.com" ``` When you're prompted to "Enter a file in which to save the key", you can press Enter to accept the default file location. 3. At the prompt, type a secure passphrase if you want to. If you don't want to have a passphrase just press enter. ## Setup in Qobra 1. Create the SSH key that will authenticate you when you send Qobra files via SFTP. Keep the public part of the key to provide it to Qobra later on. Be careful, Qobra only lets you use a single SSH key for all synchronized files with SFTP. Make sure that every data source that will push to our SFTP server use the same key. 2. Fill in the integration modal with your SSH public key. SFTP setup in Qobra 3. Push your `csv` files via SFTP, with the SSH key you created in step 1. ## Synchronization ### CSV file requirements To synchronize a file to Qobra, it must meet requirements: * the file has a `csv` format, with `;` separator * the file does not change name * the file contains a **unique identifier** column * the file contains a **name** column * the date values contained in the file must follow this format: `YYYY-MM-DD` * the user values contained in the file must be emails by default, but it can be changed in app at table creation ### Total synchronization This integrations does total synchronization, meaning the whole document will be synced at every refresh. ### Refresh Schedule By default, we perform one Full Refresh per day to keep your data up to date. However, you can customize both the frequency and timing of these refreshes to better suit your needs: * **Default Schedule**: one Full Refresh per day * **Customizable Options**: * Adjust the number of daily refreshes * Set specific hours for the refreshes to occur * Configure the schedule through your integration settings * **On-Demand Updates**: Need the latest data right away? Just click the refresh button in your integration settings Refresh schedule setup in Qobra # HRIS Connector Source: https://docs.qobra.co/integration_documentation/hris/hris_connector ## Introduction Qobra connects seamlessly to 60+ HRIS, including `Workday`, `Lucca`, `Factorial`, `ADP`, `Sage` and many more. You can access the whole list [here](https://www.kombo.dev/integrations). The integration is used to: * synchronize users, management hierarchy, user groups, user attributes * auto-provision users ## Sub-processing and security To connect to all these HRIS systems, we rely on Kombo, a german subprocessor. Kombo has been carefully audited on feature and security standards. They follow the best security standards, and are `ISO27001`, `SOC 2 type 2`, and `GDPR` compliant. You’ll find more information [here](https://security.kombo.dev/). ## Access For each HRIS, you will be able to: * choose the information scopes you give Qobra access to. * connect your HRIS to Qobra via the most secured way available. For most systems, this will be Oauth 2.0, but it can sometimes be via an api key or a service user as shown below. HRIS integration scope HRIS integration setup For the full walkthrough — setting up the connection, running a sync, and what each synchronized field maps to — see the Help Center article: [HRIS integration](https://help.qobra.co/en/articles/14430251-hris-integration). # Introduction Source: https://docs.qobra.co/integration_documentation/introductions/outbound_integration There are two main ways to automatize data outputs from Qobra: * via the Webhook * via Qobra’s APIs Data outputs diagram ## Webhook In Qobra, you can send an HTTPS request when a statement is sent to payroll. This is mainly use to push data back to your payroll system if necessary. You’ll find the webhook documentation [here](/integration_documentation/webhook/webhook). ## APIs To extract Qobra’s data to your datalake, customers use ETLs (Airbyte, Fivetran) coupled with Qobra’s API. Here are the endpoints that should be considered: * the [Fetch statements records](/api_reference/v2/endpoints/data/fetch_statements) route (v2), to retrieve statement reporting data * the [Fetch reporting records](/api_reference/v2/endpoints/data/fetch_records) route (v2), to retrieve record reporting data * the [Fetch users](/api_reference/v1/endpoints/users/fetch_users) route (v1), to retrieve all user data * the [Fetch data table records](/api_reference/v1/endpoints/data_tables/fetch_records_data) route (v1), to retrieve all data tables data Reporting is served by the v2 API — the [v2 quickstart](/api_reference/v2/quickstart) walks through a full extraction. The v1 reporting routes still work but are deprecated: if your integration already uses them, follow the [migration guide](/api_reference/v2/migration_guide). User and data table endpoints stay on v1 and are maintained. # Webhook Source: https://docs.qobra.co/integration_documentation/webhook/webhook ## Introduction Qobra offers a webhook solution, to automatize processes triggered by statements lifecycle. This will enable you, for instance, to send a HTTP request when a statement is sent to payroll. Our customer’s main use case is to send commission amount to their payroll system when a statement is sent to payroll. ## Setup Webhook setup is pretty simple, you just have to enable it in app, choose the URL you want to send a request to. You’ll find a webhook secret, used to authenticate the request on the system receiving the call. Webhook setup modal The request sent: * is using the `POST` method * is secured thanks to the webhook secret, provided in a the `X-Webhook-Secret` header * contains all statement reporting records linked to the sent to payroll statement ## Track your calls You’ll find in Qobra a space to track all webhook calls individually, with all information necessary to troubleshoot (body, response, status, url, timestamp …). Webhook call visualization You also have the option to send back the request, if necessary. # Connect an MCP client to Qobra Source: https://docs.qobra.co/mcp_documentation/connection Prerequisites, the OAuth consent flow, and adding the MCP server to Claude, ChatGPT, Dust or n8n ## Prerequisites A Qobra admin controls access in **Settings > AI**. Before you connect, the admin must: 1. Turn on AI for the company. 2. Turn on the **MCP** agent. The MCP agent can run for all users or as a pilot. In pilot mode, only the users in the pilot group can connect. Ask your admin to add you to the pilot if you're not in it. If access is denied, your MCP client shows one of these messages: | Message | Fix | | - | - | | `AI is turned off for this company.` | An admin turns on AI in **Settings > AI**. | | `MCP is turned off for this company.` | An admin turns on the **MCP** agent in **Settings > AI**. | | `MCP is limited to a pilot group and you are not in it. Ask an admin to add you to the pilot.` | An admin adds you to the MCP pilot group, or switches the agent to all users. | You authenticate as yourself. The tools only ever return data your Qobra user is allowed to see — the same role-based permissions as the app. ## Server URL The server speaks **Streamable HTTP**. Qobra runs it in two regions — pick the one that matches where your Qobra workspace is hosted (the same region you log in to the app from). If you're not sure, ask the Qobra team. | Region | Server URL | | - | - | | Europe (EU) | `https://mcp.qobra.co/mcp` | | United States (US) | `https://mcp-us.qobra.co/mcp` | There is **no API key**. Every client authenticates through the same OAuth 2.1 flow described in [Authorize](#authorize-oauth-consent) below — you only ever paste the server URL, never a secret. Everywhere below uses the EU URL as the example. If your workspace is hosted in the US, substitute `https://mcp-us.qobra.co/mcp` throughout. ## Add the server to your client Open **Settings → Connectors → Add custom connector**, give it a name (e.g. `Qobra`) and paste the server URL (`https://mcp.qobra.co/mcp`). Leave the Advanced settings (OAuth Client ID / Secret) empty. Adding the Qobra MCP server as a custom connector in Claude Custom MCP connectors live behind **Developer mode**, available on **ChatGPT Plus, Pro, Business, Enterprise and Edu** plans. Open **Settings → Connectors → Advanced** (`chatgpt.com/apps#settings/Connectors/Advanced`) and click **Create** to open the **New App** dialog. Enter a **Name** (e.g. `Qobra`). The icon and description are optional. Under **Connection**, keep **Server URL** selected and paste `https://mcp.qobra.co/mcp`. Set **Authentication** to **OAuth**. You can leave the **Advanced OAuth settings** as discovered — the server registers the client automatically. Tick **I understand and want to continue** (the standard warning for any custom MCP server), then click **Create**. Adding the Qobra MCP server as a custom connector in ChatGPT Add Qobra as a **remote MCP server** from a Dust space. Open your space's **Tools** (the *Actions* category), then **Add MCP Server** to open the **Configure MCP Server** dialog. In **URL**, paste `https://mcp.qobra.co/mcp`. Keep **Authentication** set to **Automatic**. Dust discovers that the server requires OAuth and uses dynamic client registration to obtain the credentials. Leave **Use custom headers** off. Click **Save**, complete the OAuth consent, then add the Qobra tools to an agent so it can query your Qobra data. Adding the Qobra MCP server as a remote MCP server in Dust Create an **MCP OAuth2 API** credential and turn on **Dynamic Client Registration**. Paste the server URL (`https://mcp.qobra.co/mcp`). Set the scope to `mcp:read`. Do not use `openid`. See [Scopes](#scopes). Save the credential and complete the OAuth consent. ## Authorize (OAuth consent) The server is protected by **OAuth 2.1 + PKCE** — there is no API key to copy. The flow is identical for every client (Claude, ChatGPT, Dust, n8n). On first use: The client discovers the server's OAuth metadata and registers itself automatically. The client opens the Qobra consent screen in your browser. Log in to Qobra as usual (SSO / MFA included) and click **Authorize**. The client receives a short-lived token and the Qobra tools appear in its toolbox. Tokens are scoped to your user and company, and can be revoked. ### Scopes The server has a single OAuth scope: `mcp:read`. It publishes this scope in `scopes_supported` in both its protected-resource and authorization-server metadata. * Every client gets `mcp:read`, including clients that register without a scope. The `/token` response always returns `scope: "mcp:read"`. * If your client asks for scopes, request `mcp:read` or no scope at all. * Do not request `openid`. The server doesn't support OpenID Connect and doesn't issue an `id_token`. Any scope other than `mcp:read` fails with `invalid_scope`. If a client fails to connect with `invalid_scope`, reconnect it so it rediscovers the server's scope. You don't need to recreate the credential or connector. # Qobra MCP server Source: https://docs.qobra.co/mcp_documentation/introduction Connect Claude, ChatGPT or Dust to your Qobra data through the Model Context Protocol The Qobra MCP server is enabled **per company**. A Qobra admin turns on AI and the **MCP** agent in **Settings > AI**. If the MCP agent is in pilot mode, only users in the pilot group can connect. Every tool is scoped to your Qobra user's permissions. The **Qobra MCP server** exposes a set of tools over the [Model Context Protocol](https://modelcontextprotocol.io), so an MCP client like **Claude**, **ChatGPT** or **Dust** can answer questions about your Qobra workspace and help build a demo end to end straight from a conversation. It covers data tables, integrations, users, roles, groups, quotas, compensation plans, commission letters, reports, requests, statements, imports, audit logs, Sales Coach discussions, Help Center articles and sandboxes, plus an impact analysis of everything that depends on a resource. ## What these pages document Each tool page describes a tool's **inputs** and the **shape of what it returns**. These are **MCP tools, not REST endpoints**: you never call them over HTTP yourself — you connect an MCP client (Claude, ChatGPT or Dust) to the server, and the model calls the tools for you. The `POST /mcp/tools/` shown on each page is only how we render a tool's request and response schema. ## Available tools Read tools are always exposed, scoped to the authenticated user's company and role-based permissions. Write tools are gated separately — see [Write tools (beta)](#write-tools-beta) below. | Domain | Tools | | - | - | | Identity | [`get_identity`](/mcp_documentation/tools/get_identity), [`get_company_settings`](/mcp_documentation/tools/get_company_settings) | | Sandboxes | [`list_sandboxes`](/mcp_documentation/tools/list_sandboxes) | | Data tables | [`list_data_tables`](/mcp_documentation/tools/list_data_tables), [`get_data_table`](/mcp_documentation/tools/get_data_table), [`list_data_table_records`](/mcp_documentation/tools/list_data_table_records), [`create_data_table`](/mcp_documentation/tools/create_data_table), [`create_data_table_field`](/mcp_documentation/tools/create_data_table_field), [`create_record`](/mcp_documentation/tools/create_record), [`update_record`](/mcp_documentation/tools/update_record), [`split_record`](/mcp_documentation/tools/split_record) | | Integrations | [`list_integrations`](/mcp_documentation/tools/list_integrations) | | Users | [`list_users`](/mcp_documentation/tools/list_users), [`get_user`](/mcp_documentation/tools/get_user), [`list_user_attributes`](/mcp_documentation/tools/list_user_attributes), [`create_user_attribute`](/mcp_documentation/tools/create_user_attribute) | | Roles | [`list_roles`](/mcp_documentation/tools/list_roles), [`get_role`](/mcp_documentation/tools/get_role) | | Groups | [`list_groups`](/mcp_documentation/tools/list_groups), [`get_group`](/mcp_documentation/tools/get_group), [`list_group_dimensions`](/mcp_documentation/tools/list_group_dimensions), [`create_group`](/mcp_documentation/tools/create_group), [`update_group`](/mcp_documentation/tools/update_group), [`create_group_dimension`](/mcp_documentation/tools/create_group_dimension) | | Quotas | [`list_quotas`](/mcp_documentation/tools/list_quotas), [`list_quota_values`](/mcp_documentation/tools/list_quota_values), [`create_quota`](/mcp_documentation/tools/create_quota), [`update_quota_values`](/mcp_documentation/tools/update_quota_values) | | Plans | [`list_plans`](/mcp_documentation/tools/list_plans), [`get_plan`](/mcp_documentation/tools/get_plan), [`create_plan`](/mcp_documentation/tools/create_plan), [`update_plan`](/mcp_documentation/tools/update_plan), [`create_compensation`](/mcp_documentation/tools/create_compensation) | | Commission letters | [`list_commission_letters_campaigns`](/mcp_documentation/tools/list_commission_letters_campaigns), [`list_commission_letters`](/mcp_documentation/tools/list_commission_letters), [`get_commission_letters_campaign`](/mcp_documentation/tools/get_commission_letters_campaign) | | Reports | [`list_reports`](/mcp_documentation/tools/list_reports), [`get_report`](/mcp_documentation/tools/get_report), [`get_report_data`](/mcp_documentation/tools/get_report_data), [`get_report_pivot_data`](/mcp_documentation/tools/get_report_pivot_data), [`get_report_chart_data`](/mcp_documentation/tools/get_report_chart_data), [`create_report`](/mcp_documentation/tools/create_report) | | Dashboards | [`list_dashboards`](/mcp_documentation/tools/list_dashboards), [`get_dashboard`](/mcp_documentation/tools/get_dashboard), [`create_dashboard`](/mcp_documentation/tools/create_dashboard), [`update_dashboard`](/mcp_documentation/tools/update_dashboard) | | Requests | [`list_requests`](/mcp_documentation/tools/list_requests), [`get_request`](/mcp_documentation/tools/get_request), [`create_request`](/mcp_documentation/tools/create_request), [`update_request`](/mcp_documentation/tools/update_request), [`answer_request`](/mcp_documentation/tools/answer_request) | | Sales Coach | [`list_sales_coach_discussions`](/mcp_documentation/tools/list_sales_coach_discussions), [`get_sales_coach_discussion`](/mcp_documentation/tools/get_sales_coach_discussion) | | Statements | [`list_statements`](/mcp_documentation/tools/list_statements), [`get_statement`](/mcp_documentation/tools/get_statement), [`get_statement_compensation`](/mcp_documentation/tools/get_statement_compensation), [`list_statement_records`](/mcp_documentation/tools/list_statement_records), [`list_statement_periods`](/mcp_documentation/tools/list_statement_periods), [`list_exchange_rates`](/mcp_documentation/tools/list_exchange_rates), [`update_exchange_rates`](/mcp_documentation/tools/update_exchange_rates) | | Imports | [`list_imports`](/mcp_documentation/tools/list_imports), [`get_import`](/mcp_documentation/tools/get_import) | | Audit logs | [`list_audit_logs`](/mcp_documentation/tools/list_audit_logs), [`get_audit_log`](/mcp_documentation/tools/get_audit_log) | | Impact analysis | [`list_dependencies`](/mcp_documentation/tools/list_dependencies) | | Help Center | [`list_help_center_articles`](/mcp_documentation/tools/list_help_center_articles), [`get_help_center_article`](/mcp_documentation/tools/get_help_center_article) | ### Reading a sandbox Every tool reads your company's **live environment** (its real compensation data) by default. If your company uses sandboxes and your role can read them, call [`list_sandboxes`](/mcp_documentation/tools/list_sandboxes) to discover them, then pass a sandbox's id as the `sandbox_id` argument of a list tool (`list_plans`, `list_quotas`, `list_statements`, `list_statement_periods`, `list_reports`, `list_dashboards`) to read that sandbox instead. `sandbox_id` only accepts a sandbox's id — never a sandbox name, and never the live environment's own id (omit the argument to read the live environment). Tools that take a resource id — such as `get_plan`, `get_statement` or `get_report` — need no `sandbox_id` argument: the id itself names the environment the resource lives in. The write tools that create an environment-scoped resource (`create_quota`, `create_plan`, `create_report`, `create_dashboard`) accept the same `sandbox_id` argument to say which environment the new resource lands in. Omit it to write to the live environment. `create_compensation` does not: which environment it lands in is decided by the plan it hangs off. ## Write tools (beta) The MCP server also exposes a family of **`create_*` write tools**, plus `update_group`, `update_plan`, `update_quota_values`, `update_exchange_rates`, `update_dashboard`, `update_request` and `answer_request`, so an MCP client can build a demo end to end from a conversation. This surface is **in beta and enabled per company** — where it is not enabled, the write tools are hidden at discovery, and Qobra refuses any attempt to call one with a message that says the beta is not enabled for the company. Read tools are unaffected. Every write tool is scoped to the caller's role-based permissions in the same way a read tool is — a user without write access on plans cannot call `create_plan`, exactly as they cannot create a plan from the web app. **Every write is recorded in the audit trail**, as the `Qobra MCP` agent, so a change made by a model shows up in the trail alongside changes made from the web app and by other integrations. The MCP audits writes to the **live environment** only, mirroring the audit trail's own behavior — a write into a sandbox is not audited (the trail scopes to live). The write surface today: | Domain | Tools | | - | - | | Data tables | [`create_data_table`](/mcp_documentation/tools/create_data_table), [`create_data_table_field`](/mcp_documentation/tools/create_data_table_field), [`create_record`](/mcp_documentation/tools/create_record), [`update_record`](/mcp_documentation/tools/update_record), [`split_record`](/mcp_documentation/tools/split_record) | | Groups | [`create_group`](/mcp_documentation/tools/create_group), [`update_group`](/mcp_documentation/tools/update_group), [`create_group_dimension`](/mcp_documentation/tools/create_group_dimension) | | Quotas | [`create_quota`](/mcp_documentation/tools/create_quota), [`update_quota_values`](/mcp_documentation/tools/update_quota_values) | | Plans | [`create_plan`](/mcp_documentation/tools/create_plan), [`update_plan`](/mcp_documentation/tools/update_plan), [`create_compensation`](/mcp_documentation/tools/create_compensation) | | Reports | [`create_report`](/mcp_documentation/tools/create_report) | | Dashboards | [`create_dashboard`](/mcp_documentation/tools/create_dashboard), [`update_dashboard`](/mcp_documentation/tools/update_dashboard) | | Requests | [`create_request`](/mcp_documentation/tools/create_request), [`update_request`](/mcp_documentation/tools/update_request), [`answer_request`](/mcp_documentation/tools/answer_request) | | Users | [`create_user_attribute`](/mcp_documentation/tools/create_user_attribute) | | Exchange rates | [`update_exchange_rates`](/mcp_documentation/tools/update_exchange_rates) | Each `create_*` tool creates an **empty** resource — a plan with no compensation, a quota with no target values, a data table field with no values on any record. Configuring the resource further (loading values, attaching metrics) is done from the Qobra web app, with a few exceptions: `create_dashboard` can add reports to the new dashboard and arrange its tiles in the same call, `update_dashboard` renames a dashboard, adds reports to it and arranges its tiles, `update_plan` declares a plan's membership in one call, `update_quota_values` sets a quota's target values, `update_exchange_rates` sets the rates of one exchange-rate range, `create_record` adds one record to a data table with its values filled in, `update_record` changes or archives an existing record, `split_record` splits a record between users, `create_request` opens a request with its statements, members and assignee, and `update_request` changes, closes or reopens a request. Removing a tile from a dashboard is still done from the Qobra web app. Prerequisites, the OAuth consent flow, and how to add the server to Claude, ChatGPT or Dust. # answer_request Source: https://docs.qobra.co/mcp_documentation/tools/answer_request Post a message in a request's thread, as the caller — the natural second half of create_request, and the way an admin answers a contested commission. Post a message in a request's thread, as the caller. The natural second half of [`create_request`](/mcp_documentation/tools/create_request), and the way an admin answers a rep who contested their commission. Only an open request accepts a message: a closed one is refused, saying so. A request you can't access is reported as not found. Attachments are not supported here. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Id of the request to answer — the id list\_requests returns, the one create\_request just returned, or the one in a statement's `linked_requests`. The message to post, as plain text, as in the web app: anything between `<` and `>` is removed, so write no HTML and spell comparisons out in words. It is written in the caller's name and notifies every member of the request. ## Response The request the message was added to, as it now stands. See [`list_requests`](/mcp_documentation/tools/list_requests) for the full field breakdown. The message as it now reads in the thread. The message as written, with every @-mention rendered as `@` instead of the raw user id the web app stores. The users @-mentioned in `content`, in order of first appearance; empty when the message mentions nobody. # create_compensation Source: https://docs.qobra.co/mcp_documentation/tools/create_compensation Create a compensation — one commission rule — on a plan. The compensation starts empty and in draft; no statement changes until it is published. Create a compensation — one commission rule — on a compensation plan. The compensation is created empty and in draft: no scope, no variable, no formula, no payment rule. It shows up on the plan's design view and changes nobody's statement until it is configured and published. Read it back with [`get_plan`](/mcp_documentation/tools/get_plan). Which environment it lands in comes from the plan, so a plan you can't access reads exactly like one that doesn't exist — a plan living in a sandbox you may not read included. There is no `sandbox_id` argument on this tool for the same reason. Available only where the Qobra write tools beta is enabled. Every write to the live environment is recorded in the audit trail as the `Qobra MCP` agent; writes to a sandbox are not audited. ## Parameters Id of the plan the compensation hangs off — the id list\_plans returns. The plan also decides which environment the compensation lands in, so there is no sandbox argument here. Display name of the compensation, as people will read it on the plan — 'New business commission', 'Q1 accelerator'. How often the compensation is calculated. Defaults to `EACH_MONTH`. YYYY-MM; the first month the compensation applies to. Pass it with `end_date` to bound the compensation to a window, or omit both to leave it recurring (applying every period). YYYY-MM; the last month the compensation applies to, included. Goes together with `start_date`. ## Response The compensation as it now exists — the same row get\_plan lists on the plan, so the id can be reused straight away. # create_dashboard Source: https://docs.qobra.co/mcp_documentation/tools/create_dashboard Create a dashboard, with reports already added and arranged on it, or empty. Create a dashboard — the page a set of reports is shown on — with its reports already added and arranged, or empty. It comes back with its id and a link to it; reuse that id rather than looking the dashboard up again, which can miss a creation this recent. Pass `reports` to add reports as the dashboard is created, so a board is one call rather than two; a report's `visualization_types` adds only its chart or only its pivot table — `list_reports` names what each report can render in its `available_visualizations`. Pass `layout_rows` to place them: name each tile by its `report_id`, the tiles having no `visualization_id` until this call creates them. Omit both for an empty dashboard, then use `update_dashboard` later. **Read the `outcome` on every `tile_changes` row before reporting success.** The dashboard is created either way: a report that could not be added leaves the page emptier than asked, not the call failed. Removing a tile is still a job for the Qobra web app. `layout` is the page as it was created, read back from the dashboard: where each new tile landed. Available only where the Qobra write tools beta is enabled. Every write to the live environment is recorded in the audit trail as the `Qobra MCP` agent; writes to a sandbox are not audited. ## Parameters Display name of the dashboard, as people will read it in Qobra — 'Q3 board review', 'EMEA performance'. Optional free-text note about what the dashboard is for. Reports to add to the new dashboard, each as every tile it can render unless its visualization\_types narrows it. Omit to create the dashboard empty. Id of the report to add, as list\_reports returns it. Which of its tiles to add: `chart`, `pivot_table`, or both. Omit to add every tile the report offers, which is what you want unless the caller asked for one of the two; list\_reports names what each report offers in its available\_visualizations. The dashboard's rows, top to bottom; each row lists one or more tiles left to right. An entry is a visualization\_id from get\_dashboard, or a report\_id — the report form is how you arrange tiles this same call is adding, since they have no visualization\_id until they exist, and it places every tile of that report. A row holds at most three charts or two pivot tables and is split when it overflows; a tile you leave out keeps its place, appended on a row of its own. Omit to leave the arrangement alone; pass an empty list to reset it, every tile on a row of its own. Id of the sandbox to create the dashboard in — the id list\_sandboxes returns. Omit it to create the dashboard in the live environment. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. ## Example Create a dashboard with the charts of two reports, side by side on one row, and the pivot table of a third report on a row below: ```json theme={null} { "name": "Q3 board review", "description": "Quarterly bookings and attainment", "reports": [ { "report_id": "665f1c2e8a4b3d0012a7eb01", "visualization_types": ["chart"] }, { "report_id": "665f1c2e8a4b3d0012a7eb02", "visualization_types": ["chart"] }, { "report_id": "665f1c2e8a4b3d0012a7eb03", "visualization_types": ["pivot_table"] } ], "layout_rows": [ ["665f1c2e8a4b3d0012a7eb01", "665f1c2e8a4b3d0012a7eb02"], ["665f1c2e8a4b3d0012a7eb03"] ] } ``` A `report_id` entry in `layout_rows` places every tile that report adds. A row holds at most three charts or two pivot tables, and Qobra splits a row that overflows. Read `layout` in the response to see where each tile landed. ## Response The dashboard as it now exists — the same row list\_dashboards returns, so the id can be reused straight away. Deep link to open this dashboard in the Qobra web app. What became of each report you passed, one row per tile: a report added as both a chart and a pivot table gets a row for each, and one that adds nothing gets a single row, with visualization\_type null. Read `outcome` on each before reporting success, since a call can succeed having added nothing you asked for. Empty when no reports were passed. What became of this report. "added" for a tile this call put on the dashboard; "already\_added" for one the dashboard already carried, which is how a re-sent call reads as the no-op it was; "not\_found" when no report of yours has that id; "no\_visualization" when the report has neither a chart nor a pivot table to show; "type\_not\_available" when it has tiles but none of the visualization\_types you asked for. One of: `added`, `already_added`, `not_found`, `no_visualization`, `type_not_available`. The report id you passed, echoed back as you spelled it, so the row matches your request even when report is null. The report it resolved to, named — null when it is not one you can read, since naming it would say a report you cannot see exists. The tile this row is about: chart or pivot\_table. Null when no single tile can be named — nothing was added at all. One of: `chart`, `pivot_table`. Why, as a sentence saying what to do next. Null for a tile that landed, there being nothing to act on. The dashboard's rows as they now stand, top to bottom, each tile left to right — read back from the dashboard, so it shows where every tile actually sits. A tile this call added lands on a row of its own at the end. Empty when the dashboard has no tile yet. Rows passed in layout\_rows are applied after the reports are added and come back as the store took them: a row holds at most three charts or two pivot tables and is split when it overflows, and a tile you left out keeps its place on a row of its own. The tiles on this row, left to right. Rows read top to bottom. Id of the tile, as get\_dashboard lists it. The report this tile renders. What the tile renders the report as: chart or pivot\_table. One of: `chart`, `pivot_table`. One row per layout entry you passed, saying what became of it. Empty when no layout\_rows were passed. What became of this layout entry. "placed" for a tile this call positioned; "already\_placed" when an earlier entry of the same layout already placed it, a tile sitting on one row only; "not\_found" when the entry names neither a tile of this dashboard nor a report on it. One of: `placed`, `already_placed`, `not_found`. The layout entry you passed, echoed back. Why, as a sentence saying what to do next. Null for an entry that placed its tile. # create_data_table Source: https://docs.qobra.co/mcp_documentation/tools/create_data_table Create a local data table — the structure records are then imported into. The table is created empty; add columns and load rows separately. Create a local data table — the structure records are then imported into. A data table is the root most of a compensation plan hangs off: fields, record reports, scopes and quotas all point at one. This creates the empty structure with its name field; add its other columns with [`create_data_table_field`](/mcp_documentation/tools/create_data_table_field), and load its rows by Excel import from the Qobra web app. The table matches each record's owner on the user's email address. Tables synced from an integration are created from that integration's settings instead, not here. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Display name of the table, as people will read it in Qobra — 'Deals', 'Subscriptions', 'Invoices'. The table's stable key: the identifier the public API addresses it by, distinct from the display name. Lowercase and underscore-separated, and unique across the company. Omit to derive it from the name ('Closed Deals' gives 'closed\_deals'). Label of the column holding each record's unique identifier in the source data. Defaults to 'Record ID'. Label of the column holding each record's name — what a record is listed as throughout Qobra. Defaults to 'Name'. ## Response The data table as it now exists — the same row list\_data\_tables returns, so the id can be reused straight away. Deep link to open this data table in the Qobra web app. One of: `local`, `integration`. Always `local` for a table created here. Null — a table created here has no source integration. The table's views. A just-created table has one: its `Default` view, created with the table. Pass its id as `view_id` to list\_data\_table\_records. The columns on the table. A just-created table has only its name field. Stable technical key for this field in Qobra's public API. Field data type. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. When type is object, the table this field points to; null otherwise. Whether create\_record / update\_record can set this column. False for a lookup, whose value is read from the linked record, and for a link to another table. # create_data_table_field Source: https://docs.qobra.co/mcp_documentation/tools/create_data_table_field Add one column to a data table and get the table back as it now stands — with its full field schema, exactly as get_data_table returns it. Add one column to a data table, and return the table as it now stands. The column is created empty on every existing record: this sets no values and no default. It is added to the table's views, to the scopes reading that table and to the plans' simulation pipelines, so it is available to a compensation and to a report straight away — load its values by Excel import from the Qobra web app, or through Qobra's public API. The table comes back with its full field schema, exactly as [`get_data_table`](/mcp_documentation/tools/get_data_table) returns it, so adding several columns in a row needs no read between them. A column synced from an integration, and a link to another data table, are set up on the table's page in the Qobra web app instead, not here. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Id of the data table to add the column to — the id list\_data\_tables returns, or the one create\_data\_table just returned. Never a table name. Display name of the column, as people will read it in Qobra — 'Amount', 'Closing date', 'Owner', 'Stage'. What the column holds. `amount` is monetary and `percentage` a ratio, where 0.15 reads as 15%; `float` is a plain number, for a count or a quantity. `user` holds a Qobra user, matched on their email when records are imported. `picklist` is one choice out of a fixed list and `multipicklist` several — both need `options`. A link to another data table is not created here. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`. The column's stable key: the identifier the public API and an Excel import address it by, distinct from the display name. Lowercase and underscore-separated, and unique within the table. Omit to derive it from the name ('Closing date' gives 'closing\_date'). The labels people choose from, for a picklist or a multipicklist column — required there, and refused on every other type. These exact labels are what a record's value is matched on when rows are imported. For an amount column, the key of the column each row's currency code is read from — a sibling field's key on an imported table, a payload key on one fed through Qobra's public API. Not a currency code itself, and refused on any other type. Omit it to denominate every row in the company currency. Optional free-text note about what the column holds. ## Response The data table the column was added to, with its full field schema as get\_data\_table returns it — the new column included. Read it to see the table as it now stands. See [`get_data_table`](/mcp_documentation/tools/get_data_table) for the field breakdown. The column just created — which of `data_table.fields` is the new one, so its id is at hand without matching on a name. # create_group Source: https://docs.qobra.co/mcp_documentation/tools/create_group Create a group — one value of an existing dimension — added straight away to the dimension's picklist in every reporting table. Create a group — one value of an existing dimension ('EMEA' under 'Region'). The dimension has to exist first; create it with [`create_group_dimension`](/mcp_documentation/tools/create_group_dimension). The group is created empty: this tool attributes no users to it. The new value is added to the dimension's picklist in every reporting table, so reports can break down by it straight away. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Id of the dimension the group belongs to — the id list\_group\_dimensions returns, or the one create\_group\_dimension just returned. Never a dimension name. Display name of the group, one value of its dimension — 'France' under a 'Country' dimension. The group's stable key: the identifier an integration matches it on, distinct from the display name. Lowercase and underscore-separated by convention (e.g. 'emea\_north'). ## Response The group as it now exists — the same row list\_groups returns, so the id can be reused straight away. Deep link to open this group in the Qobra web app. The group's stable key — the identifier used to match it when synced from an integration, distinct from its display name. Number of users currently in the group. Zero for a just-created group. # create_group_dimension Source: https://docs.qobra.co/mcp_documentation/tools/create_group_dimension Create a group dimension — the grouping axis your company's groups hang off — added straight away as a picklist field on every reporting table. Create a group dimension — a grouping axis your company's groups hang off. A dimension holds no members itself: create the axis here, then call [`create_group`](/mcp_documentation/tools/create_group) once per value it takes ('Country' → 'France', 'Spain'). The new dimension is added as a picklist field to every reporting table, so it can be used as a report breakdown straight away. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Display name of the grouping axis, as people will read it in Qobra — 'Country', 'Segment', 'Squad'. The dimension's stable key: the identifier an integration matches it on, distinct from the display name. Lowercase and underscore-separated by convention (e.g. 'sales\_region'). Optional free-text note about what the axis groups. ## Response The dimension as it now exists — the same shape list\_group\_dimensions returns, so the id can be reused straight away. The dimension's stable key — the identifier used to match it when synced from an integration, distinct from its display name. Number of groups that belong to this dimension. Zero for a just-created dimension. # create_plan Source: https://docs.qobra.co/mcp_documentation/tools/create_plan Create a compensation plan — the container its compensations hang off. The plan is created empty; add compensations with create_compensation. Create a compensation plan — the container its compensations hang off. The plan is created empty: no compensation, nobody attributed to it. Add compensations with [`create_compensation`](/mcp_documentation/tools/create_compensation), then open the plan with [`get_plan`](/mcp_documentation/tools/get_plan) or find it again in [`list_plans`](/mcp_documentation/tools/list_plans). Available only where the Qobra write tools beta is enabled. Every write to the live environment is recorded in the audit trail as the `Qobra MCP` agent; writes to a sandbox are not audited. ## Parameters Display name of the plan, as people will read it in Qobra — 'Enterprise AE 2026', 'SDR H1'. Id of the section to file the plan under — the id a plan's section carries in list\_plans. Omit it to leave the plan unfiled. Never a section name. Id of the sandbox to create the plan in — the id list\_sandboxes returns. Omit it to create the plan in the live environment. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. ## Response The plan as it now exists — the same row list\_plans returns, so the id can be reused straight away. Deep link to open this plan in the Qobra web app. The section the plan is filed under; null when unfiled. Zero for a just-created plan. # create_quota Source: https://docs.qobra.co/mcp_documentation/tools/create_quota Create a quota — the definition of a target users are measured against. Starts empty; set its target values with update_quota_values. Create a quota — the definition of a target users are measured against. This call creates the quota's *definition* and its default view, and sets no target values: the quota starts empty and every user reads as unset. Set them with [`update_quota_values`](/mcp_documentation/tools/update_quota_values), passing the id this call returns. The quota's frequency, type and currency type can only be changed afterwards on its page in the Qobra web app. Quotas synchronized from an integration are not created here either — set those up in the Qobra web app. Available only where the Qobra write tools beta is enabled. Every write to the live environment is recorded in the audit trail as the `Qobra MCP` agent; writes to a sandbox are not audited. ## Parameters Display name of the quota, as people will read it in Qobra — 'New Business ARR', 'Number of demos booked'. The period one target covers: monthly, quarterly, semesterly or annually. It fixes the granularity every target value is written at (2026-03, 2026-Q1, 2026-S1, 2026), so it is the one field to get right before any value is loaded. One of: `monthly`, `quarterly`, `semesterly`, `annually`. Unit of the target value: amount (monetary, carries a currency), percentage, or float (a plain number — use it for counts, such as a number of deals). One of: `amount`, `percentage`, `float`. Default currency for an amount quota: `company` (the company currency), `user` (each user's payment currency) or `calculation` (the user's calculation currency). Required even for a percentage or float quota, where it is not meaningful — pass `company` there. One of: `company`, `user`, `calculation`. Optional free-text note about what the quota targets. Id of the sandbox to create the quota in — the id list\_sandboxes returns. Omit it to create the quota in the live environment. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. ## Response The quota as it now exists — the same row list\_quotas returns, so the id can be reused straight away. Deep link to open this quota in the Qobra web app. One of: `amount`, `percentage`, `float`. One of: `company`, `user`, `calculation`. The quota's default view, minted at creation time. # create_record Source: https://docs.qobra.co/mcp_documentation/tools/create_record Add one record to a data table — a deal, an account, any row. One record per call, meant for adjustments. Add one record to a data table — a deal, an account, any row. Call get\_data\_table first: its `fields` give the ids to write, the type each value must take, the labels a picklist accepts, and `editable`, false for the fields no write can set (lookups and links to another table). Its `adjustment_reasons` say whether a `reason` is required here. One record per call, meant for adjustments: loading many records is an import, done from the Qobra web app. Statements are not recalculated by this call, just as when a record is added in the web app — they pick it up at the next automatic recalculation, or when someone refreshes them. Requires write permission on data tables. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Id of the data table to add the record to — the id list\_data\_tables returns. Local and integration tables are both accepted. The record's cells, one per field to set. A field left out takes its default; the name field defaults to 'New record'. Id of the column — a field id get\_data\_table returns. Only a field whose `editable` is true can be written. The cell's new value, in the shape the read tools serve it: a number for amount / float / percentage (a percentage is the ratio, 0.8 for 80%), true/false for bool, an ISO date for date, text for string, one option label for picklist, a list of option labels for multipicklist, and a user id or email for user. null empties the cell. Currency code of an amount (EUR, USD…). Omit it to keep the cell's currency; ignored on every other type. Why the record is added — one of the table's `adjustment_reasons` from get\_data\_table. Required only when that list is not null; omit it otherwise. Optional note kept in the record's history. The call is refused, with a message naming the problem, when a `field_id` is unknown or listed twice, when a field is a lookup or a link to another table, when a value does not match its field's type or picklist options, or when the table requires a `reason` and none from its `adjustment_reasons` was passed. ## Example ```json theme={null} { "data_table_id": "665f1c2e8a4b3d0012a7e9f1", "values": [ { "field_id": "665f1c2e8a4b3d0012a7e9f2", "value": "Acme renewal" }, { "field_id": "665f1c2e8a4b3d0012a7e9f3", "value": 12000, "currency": "EUR" }, { "field_id": "665f1c2e8a4b3d0012a7e9f4", "value": "2026-09-15" }, { "field_id": "665f1c2e8a4b3d0012a7e9f5", "value": "Closed won" } ], "reason": "Missing deal", "comment": "Signed offline, not yet in the CRM." } ``` ## Response The table the record was created in, whose `fields` header names the record's values in order — the same row list\_data\_tables returns. See [`list_data_tables`](/mcp_documentation/tools/list_data_tables) for the full field breakdown. The record as it now exists — the same row list\_data\_table\_records returns, so the id can be reused straight away. Its `values` are read in the order of `data_table.fields`. See [`list_data_table_records`](/mcp_documentation/tools/list_data_table_records) for the full field breakdown. # create_report Source: https://docs.qobra.co/mcp_documentation/tools/create_report Create an empty report — no metrics, fields or chart attached yet. Attach the rest from the Qobra web app or by prompting the Analyst. Create an empty report — no metrics, fields or chart attached yet. The report comes back with its id and a link to it; reuse that id rather than looking it up again, which can miss a creation this recent. Attaching metrics or data table fields, configuring a pivot table or a chart, is not something this tool does — prompt the Analyst to fill in the report it just created, or do it from the Qobra web app. A record report tracks the rows of one data table, named through `tracked_data_table_id`; a statement report tracks payout statements and takes no data table. Available only where the Qobra write tools beta is enabled. Every write to the live environment is recorded in the audit trail as the `Qobra MCP` agent; writes to a sandbox are not audited. ## Parameters Display name of the report, as people will read it in Qobra — 'Q3 pipeline', 'New Business deals'. What the report is built on: `statement` (payout statements) or `record` (the rows of a data table, named by `tracked_data_table_id`, which is then required). One of: `statement`, `record`. Optional free-text note about what the report is for. Id of the data table this report tracks — the id list\_data\_tables returns. Required when `report_type` is `record`, and refused otherwise. Id of the section to file the report under — the id a report's section carries in list\_reports. Omit it to leave the report unfiled. Never a section name. Id of the sandbox to create the report in — the id list\_sandboxes returns. Omit it to create the report in the live environment. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. ## Response The report as it now exists — the same row list\_reports returns, so the id can be reused straight away. See [`list_reports`](/mcp_documentation/tools/list_reports) for the full field breakdown. # create_request Source: https://docs.qobra.co/mcp_documentation/tools/create_request Open a request about one or more statements or deals, with its first message, members and an assignee, in one call. Open a request — the collaboration thread people settle a commission question in. Optionally about statements: each one in `statements` opens attached to the request, exactly as from the statement page in the web app, and a record narrows it to one deal of that statement. One request can cover several statements — open one per question, not one per statement. A statement is linked once, about one of its records at most. The caller is the request's author, and `message` is posted in their name. Only a request's members are notified of its messages, and most users see only the requests they belong to — so list in `member_ids` everyone who should follow it: the user whose statement it is about, anyone `message` @-mentions. A mention alone neither adds nor notifies them. Close, reassign or re-file the request later with [`update_request`](/mcp_documentation/tools/update_request). Attachments are not supported here. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Title of the request, as it reads on the requests board — 'January commission looks low'. Keep it short; the detail goes in `message`. First message to post in the thread, written as the caller. Plain text, as in the web app: anything between `<` and `>` is removed, so write no HTML and spell comparisons out in words. Omit to open the request with an empty thread. The statements the request is about — one user in one month each, optionally narrowed to one of its records. Omit for a request about no statement in particular. Id or exact email of the user whose statement it is. Month of the statement, as YYYY-MM (e.g. '2026-03'). The one record of this statement the request is about — a deal, most often. Omit to link the statement as a whole. Id of the scope the record was frozen into — the `id` of one of get\_statement\_compensation's scopes. Id of the record — the `record_id` list\_statement\_records returns for that scope. Id of the topic to file the request under — one of `requests.topics` from get\_company\_settings. Omit to file it under no topic. The call is refused when the company defines no topics, or when the id names none of them; the refusal lists the available topics and their ids. Ids or emails of the people to add to the request as participants, beside its author — as list\_users returns them. Emails match case-insensitively. Each one can read and answer the thread, is notified they were added, and appears in the request's `members`. Omit to open the request with its author as the only member. Id or email of the person to assign the request to — the one expected to handle it, as list\_users returns it. They become a member and are notified it is theirs. Omit to leave it unassigned, as the web app opens a request. ## Response The request as it now exists — the same row list\_requests returns, so the id can be reused straight away. Deep link to open this request in the Qobra web app. The #-style reference shown in the UI. Admin-configured category used to classify the request; null if none. open (awaiting handling) or closed (resolved). One of: `open`, `closed`. The linked deal/record when the statement is record-based; null otherwise. The first message, when one was posted with the request; null when the request was opened with no message. The message as written, with every @-mention rendered as `@` instead of the raw user id the web app stores. See `mentions` for the ids and emails behind those names. The users @-mentioned in `content`, in order of first appearance; empty when the message mentions nobody. A mention whose id matches no user is left as a raw handle in `content` and is absent here. # create_user_attribute Source: https://docs.qobra.co/mcp_documentation/tools/create_user_attribute Create one user attribute — a column on your company's people. The attribute is opened empty on every existing user, so creating it touches the whole user table. Create one user attribute — a column on your company's people. The schema of what the company records about a person (Segment, Hiring date…), which is what scopes filter on and what a report breaks down by. The attribute is company-wide: it is not scoped to a sandbox, and it shows up in [`list_user_attributes`](/mcp_documentation/tools/list_user_attributes) last in the display order. **This writes to every existing user.** The attribute is opened on each one of them at creation — empty, except a `bool`, which starts at false for everyone — and an `amount` attribute also records the company currency there. So creating an attribute touches the whole user table, not just the definition. Load the values by Excel import from the Qobra web app, which is also where a default value is set. Creating the *people* is not done here: they come in by Excel import from the Qobra web app, which is also where an attribute synced from an HRIS or a CRM is set up. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Display name of the attribute, as people will read it in Qobra — 'Segment', 'Hiring date', 'Region'. The attribute's stable key: the identifier the public API and an Excel import address it by, distinct from the display name. Lowercase and underscore-separated, and unique across the company's attributes ('Hiring date' gives 'hiring\_date'). The public API already serves every user an `id`, `email`, `status`, `name`, `role`, `currency` and `calculation_currency`, so those seven are taken. What the attribute holds. `amount` is monetary and `percentage` a ratio, where 0.15 reads as 15%; `float` is a plain number, for a count or a quantity. `user` holds a Qobra user, matched on their email when people are imported. `picklist` is one choice out of a fixed list and `multipicklist` several — both need `options`. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`. The labels people choose from, for a picklist or a multipicklist attribute — required there, and refused on every other type. These exact labels are what a user's value is matched on when people are imported. Include this attribute in the company's user exports. Off by default, as it is in Qobra. Refuse two users the same value — for an employee number or a payroll id, not for a segment everyone shares. Applies to a `string` or a `float` attribute only. ## Response The attribute as it now exists — the same row list\_user\_attributes returns, so the id can be reused straight away. What the company says this attribute means, in their own words. Null when they wrote none. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`. Selectable option labels for a picklist or multipicklist attribute; null for every other type. True when the value cannot be edited in Qobra — it is owned by the source system that syncs it. The company connection this attribute is synced from; null for a just-created attribute maintained in Qobra itself. # get_audit_log Source: https://docs.qobra.co/mcp_documentation/tools/get_audit_log Full detail of one audit-trail entry, including its change payload. A list\_audit\_logs row plus `changes` — the action's field-level before/after diff as a clean list — and its `description`. Reach for it after list\_audit\_logs surfaces an entry whose change detail you need to explain. For the affected resource's own current state, use that resource's tool. ## Parameters Id of the audit-trail entry to inspect — the id returned by list\_audit\_logs. ## Response Deep link that opens this audit-log entry's detail modal in the Qobra web app. When the action happened (UTC, ISO-8601). Coded name of the audited event, e.g. 'UserUpdated' or 'CompensationRuleUpdated' — the precise action taken. Id of the affected resource; null when not applicable. Human-readable name of the affected resource, if known. ObjectId of the actor; feed it to get\_user for detail. Actor's email as recorded at action time — the identifier to recognize the user by. Null for a system/script action. Actor's role name as recorded at action time. The surface the action was performed through: `Qobra UI` is the web app, its in-app AI agents (the Analyst, the Architect) included; `Qobra MCP` is an MCP client acting as the actor; `Qobra Onboarding` is the self-serve onboarding agent. Null on entries written before the surface was recorded: unknown, not the web app. One of: `Qobra UI`, `Qobra MCP`, `Qobra Onboarding`. The action's field-level changes as a clean before/after list. Empty when the action carries no field diff (e.g. a create/delete, or a payload-less event). Name of the field that changed. Value before the change; null when unset or on a create. Value after the change; null when unset or on a delete. # get_commission_letters_campaign Source: https://docs.qobra.co/mcp_documentation/tools/get_commission_letters_campaign Return one commission-letter campaign's meta: overall status, envelope counts, how pre/post signers are chosen, and CC slots. Return one commission-letter campaign's detail, without its per-recipient letters. The natural follow-up to `list_commission_letters_campaigns`: take a campaign row and read its meta — template, subject, send choice, when and by whom it was sent, a derived `overall_status`, `envelope_counts`, how the pre/post signers are chosen and the campaign's CC slots. Every person is surfaced by email, never a bare id. Each recipient's letter (envelope status, signers, people in CC) is read page by page with `list_commission_letters`. A campaign you can't access is reported as not found. The letters' PDF content is omitted. ## Parameters Id of the campaign to read — the id list\_commission\_letters\_campaigns returns. ## Response Deep link to open this campaign in the Qobra web app. How the letters are delivered: email (plain email) or esignature (electronic signature). One of: `email`, `esignature`. One of: `draft`, `generating`, `sent`, `awaiting_signatures`, `completed`, `action_needed`. Letters tallied per e-signature envelope status; empty for e-mail campaigns (they have no envelopes). How the pre-signer is chosen: specific (a fixed named person) or dynamic (resolved from the org chart, 1-3 levels above the letter owner). One of: `specific`, `dynamic`. Same as presigner\_mode, for the post-signer. One of: `specific`, `dynamic`. Campaign-level CC slots. Each is either a specific employee or a manager N levels above the letter owner. How this CC is chosen: specific (a fixed named person) or dynamic (resolved from the org chart, 1-3 levels above the letter owner). One of: `specific`, `dynamic`. One of: `directly_above__user`, `two_levels_above__user`, `three_levels_above__user`. # get_company_settings Source: https://docs.qobra.co/mcp_documentation/tools/get_company_settings Return your company's parameter sheet — statement periods, currencies, AI agents, enabled modules — one block per settings section. Return how your company is configured, one block per settings section. Reach for this when the question is about the *setup* rather than the figures — "are draws activated?", "who can use the Sales Coach?", "since when do statements exist?" — and when a tool comes back empty and you need to know whether the feature is simply off. [`get_identity`](/mcp_documentation/tools/get_identity) already carries the fiscal calendar, the one piece that conditions every period label; the `profile` block repeats it so the sheet reads on its own. The currency and the timezone here are the *company's* — the ones on identity's user are what you personally are paid in and work in. Each block carries the settings page it is edited on, so an answer about a setting can point at where to change it. A null `settings_url` means that section is not self-serve and Qobra support changes it. **An optional module that is unusable is absent, not a block of falsy fields.** `requests`, `validation_workflow`, `draws` and `licences` are null in that case, and `unavailable_modules` says why for each: the subscription tier does not include it (the user has to talk to Qobra about upgrading — read `package` for the tier they are on), it is included but switched off, or you personally may not read it. Each row's `remedy` is the sentence to tell the user. None of them is a reason to retry. Takes no arguments: the company is the one your session is scoped to. For exchange rates between the available currencies, and for what a *user* is paid in, see the currency fields on the user and statement tools. ## Response The company's Qobra subscription tier: core, advanced, scale, or others. What a tier includes is why a module below may be absent — see `unavailable_modules`. One of: `core`, `advanced`, `scale`, `others`. Always present. IANA timezone the company's dates are expressed in. Same shape as on `get_identity`: the fiscal year in progress, as a plain month range. Repeated here so the sheet reads on its own. The fiscal year label the current calendar day falls in, formatted like `FY2027`. First month of the fiscal year, `YYYY-MM`. Last month of the fiscal year, `YYYY-MM`. Deep link to the company settings page. Always present. `YYYY-MM`; the earliest period the company's statement navigation offers. A display floor, not an existence boundary — statements before it can still exist, and `list_statements` will return them if you ask for that period. Null means no floor was configured. How many periods beyond the current one are opened ahead of time. Hour of the day (0-23, UTC) statements are recalculated automatically — when figures refresh without anyone acting. Always null: these are set by Qobra, not on a settings page. Ask Qobra support to change them. Always present. The currency the company consolidates totals in. Whether commissions may be computed in a currency other than the one they are paid in — the multi-currency feature AND the company's own switch. Every currency the company has exchange rates for. Empty means it operates in a single currency. Deep link to the currencies settings page. Always present. One row per AI agent Qobra ships. Which AI agent this row is about: `sales_coach` (explains a rep their statement), `analyst` (answers reporting questions), `architect` (builds compensation plans), `mcp` (the Qobra MCP server). One of: `sales_coach`, `analyst`, `architect`, `mcp`. Deep link to this agent's settings page. Whether the agent is usable in this company — the subscription feature AND the company's AI switches, not one of the two. False is the answer to "why can nobody use it?". Whether *you*, the calling user, may use the agent — `available` narrowed by the pilot group when the agent is in pilot mode. Rollout mode: `all` (everyone in the company) or `pilot` (only the users listed in `pilot_users`). One of: `all`, `pilot`. The users allowed to use the agent while it is in pilot mode. Empty when mode is `all` — the whole company is then allowed — and empty as well when you may not read the company's AI settings, which never affects your own `available_to_you`. Deep link to the AI settings page. Null when requests are unusable — see `unavailable_modules`. The topics a request can be filed under. Empty when the company defined none. Deep link to the requests settings page. Null when the approval workflow is unusable — see `unavailable_modules`. Deep link to the validation-workflow settings page. Null when draws are unusable — see `unavailable_modules`. Always null: draws are switched on by Qobra, not on a settings page. Ask Qobra support to modify this. Null when seats are unusable, or when you may not read the company's subscription — see `unavailable_modules`. Whether seats are enforced. When true, only a licensed user's commissions are computed. How many seats the company has been granted. Deep link to the subscription settings page. One row per null block above, saying why it is null and what would bring it back. Empty when every module is available to you. Which block is absent: `requests` (collaboration threads), `draws` (recoverable advances), `validation_workflow` (statement approval), `licences` (seats). One of: `requests`, `draws`, `validation_workflow`, `licences`. `not_in_package` (the subscription tier does not include it), `switched_off` (included but nobody turned it on), `not_yours_to_read` (it may well be on — you lack the permission). One of: `not_in_package`, `switched_off`, `not_yours_to_read`. What to tell the user, in a sentence. Says whether this is a package upgrade, a settings change, or a word to Qobra support — never retry the tool. # get_dashboard Source: https://docs.qobra.co/mcp_documentation/tools/get_dashboard Read one dashboard's full detail: its chart and pivot tiles, their row-by-row layout, and the configuration of every report the tiles render. Return one dashboard's full detail: its tile layout and the reports it shows. The natural follow-up to `list_dashboards` when you need to understand an existing dashboard — not just which reports it groups, but how they are laid out and what each report is built on. On top of the `list_dashboards` row it adds `visualizations` (the flat list of chart / pivot tiles), `layout` (their arrangement, row by row) and `reports` (the full detail of every distinct report the tiles render — the data table each is built on, its type, who can view it, and its available visualization types). You only see the dashboards you have access to — every one of the company's at full dashboard access, otherwise only those shared with you (by role or individually). A dashboard you can't reach is reported as not found. `reports` describes how each tile is built, never its data: the computed rows stay behind `get_report_data`, which you reach only for a report granted to you directly — a dashboard grant alone gets you the tiles' chart / pivot values (`get_report_chart_data` / `get_report_pivot_data`), not the granular rows behind them. ## Parameters Id of the dashboard to read — the id list\_dashboards returns. ## Response Deep link to open this dashboard in the Qobra web app. The chart / pivot-table tiles the dashboard shows, each referencing the report it is built on. Stable id of this tile. The layout rows reference tiles by this id, so it is how you place a tile in the dashboard's arrangement. Id of the report this tile renders. Null for a tile not backed by a report. The tile's display name (usually the report's name). One of: `chart`, `pivot_table`. The dashboard's tile arrangement, top to bottom — each row lists the tiles placed on it, left to right. This is how the visualizations are laid out on the page. The visualization\_ids sitting side by side on this row, in left-to-right order. Cross-reference each id with the matching entry in the dashboard's 'visualizations' to know which report tile it is. Full detail of every distinct report the dashboard's tiles are built on: the tracked data table for record reports, its type, who can view it, and every tile type it can render — shown on this dashboard or not. Deduplicated — a report shown as both a chart and a pivot table appears once, and the 'visualizations' list above is where each tile's own type is named. Deep link to open this report in the Qobra web app. What the report is built on: statement (payout statements) or record (the rows of the tracked\_datatable). One of: `statement`, `record`. Source data table tracked by a record report, with its id and name; null for statement reports. The dashboard tiles this report can render: `chart` when a chart is configured on it, `pivot_table` when a pivot table is. Empty when neither is — the report then has no tile to show on a dashboard. The section the report is filed under (id and name), or null when unfiled. Who can view the report, split into roles and users. Each entry carries its id, name, and access level. Only returned to callers with write access on reports; null for read-only callers. One of: `read`, `write`, `delete`. One of: `read`, `write`, `delete`. One of: `read`, `write`, `delete`. # get_data_table Source: https://docs.qobra.co/mcp_documentation/tools/get_data_table Get one data table's field schema by id, including picklist option labels, source, public-API key, and any synced integration. Get one data table's full field schema, resolved by name or id. Returns the same shape as a `list_data_tables` row — source, public-API key, integration and fields — plus, for every PICKLIST / MULTIPICKLIST field, its selectable option labels. Reach for it to learn a table's columns and the exact option labels to filter its records on; use `list_data_tables` to discover which tables exist. You only see the data tables you have access to. An id that names no accessible table is an error — a table outside your scope reads exactly like one that does not exist. ## Parameters Id of the data table to fetch — the id list\_data\_tables returns. ## Response Deep link to open this data table in the Qobra web app. One of: `kombo`, `salesforce`, `pipedrive`, `microsoft_dynamics`, `hubspot`, `odoo`, `zoho`, `attio`, `postgresql`, `gcp-postgresql`, `snowflake`, `snowflake-keys`, `redshift`, `microsoft-azure-sql`, `microsoft-azure-synapse`, `bigquery`, `s3`, `sftp`, `netsuite`, `public_api`. The views the caller may query for this table's records — pass one's id as `view_id` to list\_data\_table\_records to narrow the rows. Empty when no view is relevant. Stable technical key for this field in Qobra's public API (use it as a durable reference; the display name can change). Field data type: amount, percentage, float, string, bool, date, picklist, multipicklist, user, or object (a reference to another data table — see linked\_to). One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. When type is object, the table this field points to; null otherwise. Name of the linked table. `null` when the caller has no access to the linked table (for example, when the link is visible only through a shared view on the current table). `id` is always returned. Whether create\_record / update\_record can set this column. False for a lookup, whose value is read from the linked record, and for a link to another table. Selectable option labels for a PICKLIST / MULTIPICKLIST field, in definition order; null for every other field type. Use these exact labels when filtering records on this column. The reasons a manual edit of this table's records must cite, passed as `reason` to create\_record / update\_record; null when an edit here needs none. # get_group Source: https://docs.qobra.co/mcp_documentation/tools/get_group Return a single group with its full member list: name, key, dimension, and every past and present user attribution with its membership window. Return a single group with its full member list. Beyond the group's identity (name, key, dimension), the result joins in every user attribution — past and present — as an `{id, email, name}` user reference plus a `YYYY-MM` membership window; a null departure means the membership is still ongoing. ## Parameters Id of the group to look up — the id list\_groups returns. ## Response Deep link to open this group in the Qobra web app. The group's stable key — the identifier used to match it when synced from an integration, distinct from its display name. The dimension the group belongs to (the kind of grouping, e.g. 'Country' or 'Team'). Null if the dimension is unknown. Number of users currently in the group (attributions active as of today). Past and future members are excluded. Every user attribution on the group, past and present, each with its membership window. A user with several stints appears once per attribution. YYYY-MM; the month the user joined the group. YYYY-MM; null while the membership is still ongoing. # get_help_center_article Source: https://docs.qobra.co/mcp_documentation/tools/get_help_center_article Return one Help Center article's full markdown body by id, including its title, author description, and source URL, ready to quote or summarize. Return one Help Center article's full markdown body, by id. The natural follow-up to `list_help_center_articles`: take a row's `id` and read the whole article — its title, author description, full body (markdown) and source URL. An article in a collection the caller isn't entitled to reads as not found, exactly like an unknown id. ## Parameters Id of the article to read — the id list\_help\_center\_articles returns. ## Response The author-written description. Public URL of this Help Center article (an external help.qobra.co link, not a Qobra web-app page); null if unpublished. # get_identity Source: https://docs.qobra.co/mcp_documentation/tools/get_identity Return your own identity, company and access scope: who you act as, your language and timezone, and which resources you may query. Return your own identity, company and access scope. Use this first to learn *who you are acting as*, *which company you are in*, and *what you are allowed to query*. The caller is returned as the same compact summary `list_users` gives (name, email, role, currencies, status), plus your own `language` and `timezone` — preferences that say how to answer *you* and appear here and nowhere else. The company is deliberately minimal: id, name and the fiscal calendar. Pass your own `role` id to `get_role` to read the permissions behind your limits. Read the fiscal calendar before interpreting any period. Every `YYYY`, `YYYY-SX` and `YYYY-QX` label another tool returns is numbered on it, and the company's own fiscal year need not start in January. A response of `FY2027, 2026-02 → 2027-01` says the current fiscal year is *FY2027* and spans February 2026 through January 2027. For the rest of the configuration — statement periods, currencies, the AI agents your company runs, which optional modules are switched on — call [`get_company_settings`](/mcp_documentation/tools/get_company_settings). For the connected integrations, call [`list_integrations`](/mcp_documentation/tools/list_integrations). `access` carries one key **per resource you can actually reach** (`statement`, `request`, `quota`, `data_table`, `report`, `dashboard`): how broadly you see it (`scope` plus a plain-language `description`) and, for statements and requests, the saved `views` you may pass to the matching list tool. Each view also carries `access` — the roles and users who can read it — when you are allowed to share that resource. A resource you have no access to carries **no key at all** — if a key is missing, do not try to query that resource. Reports and dashboards report `all` or `shared` only, like quotas: each *is* its own view, so a share names the resource itself. A caller who reaches a report **only** through a shared dashboard carries no `report` key — the values its tiles display are named in that dashboard's `description` instead of inventing a fifth `scope`. `shared_resources` names the resources shared with you *individually* — the grants that reach past your role, already counted in `access` above. Pass your own `role` id to [`get_role`](/mcp_documentation/tools/get_role) for the other channel: what is shared with everyone holding your role. The rows follow the same shape `get_role` returns. Sharing is a live-environment concept, so those are the live ones. Takes no arguments: the identity is read from the authenticated session, not passed in. For the *full* profile (managers, groups, plan attributions, custom attributes), call `get_user` with your own id or email. ## Response Same compact fields as each item in [`list_users`](/mcp_documentation/tools/list_users)'s response (`id`, `resource_url`, `name`, `email`, `status`, `role`, `manager`, `payment_currency`, `calculation_currency`, `licensed`, `license_missing`, `integrations`, `last_synced_at`), plus two fields unique to this tool: The language you set in Qobra — the one to answer you in when nothing else says otherwise. IANA timezone you read dates in. The company's fiscal year in progress, as a plain month range. The numbering basis of every period label another tool returns — a `YYYY-Q1` or `YYYY-S1` is counted on it, and a fiscal year need not start in January. The fiscal year label the current calendar day falls in, formatted like `FY2027`. First month of the fiscal year, `YYYY-MM`. Last month of the fiscal year, `YYYY-MM`. One key per resource you can actually reach (`statement`, `request`, `quota`, `data_table`, `report`, `dashboard`): how broadly you see it (`scope` plus a plain-language `description`) and, for statements and requests, the saved `views` you may pass to the matching list tool. A resource you have no access to carries no key at all. Full per-resource schema: [Access object reference](/mcp_documentation/reference/access_object). Resources shared with you *individually*, on top of whatever your role grants — call [`get_role`](/mcp_documentation/tools/get_role) with your own `role` id for the other channel, which covers everyone holding it. This is what lets you reach a view your role alone would deny, so `access` above already accounts for it. Empty when nothing is shared with you individually. Full field reference (`resource`, `resource_type`, `parent_name`, `access`): [`get_role`](/mcp_documentation/tools/get_role)'s `shared_resources` — same shape. Live environment only. # get_import Source: https://docs.qobra.co/mcp_documentation/tools/get_import Return the full detail of one data import: per-batch status, progress, record counters, error detail and category, rejected rows and a deep link to the run in the Qobra web app. Return the full detail of one data import, including its per-batch breakdown. A list\_imports row plus `configs` — one entry per resource batch, each with its own status, progress, record counters (created / updated / deleted / skipped), error state, the connection it synced from and the rows it rejected or skipped (the first 20, each with its reason). Reach for it after list\_imports surfaces a run whose outcome you need to explain, e.g. why an integration refresh failed or why some users did not come in. You only see imports that touched a resource type you may read; an import whose every resource type is outside your permissions is reported as not found. ## Parameters Id of the import to inspect — the id returned by list\_imports. ## Response Deep link that opens this import expanded in the Qobra web app, on the history page of a resource type it touched. Null only for an import with no batch yet, which no history page lists. Overall status: 'scheduled', 'started' or 'finished'. One of: `scheduled`, `started`, `finished`. What kicked the import off, e.g. 'scheduled\_refresh', 'integration\_refresh' or 'quota\_sync'. Null when unknown. One of: `scheduled_refresh`, `integration_refresh`, `integrations_with_frequency_refresh`, `braqoffice_refresh`, `users_creation`, `user_integration_sync`, `user_file_import_refresh`, `user_attributes_creation`, `groups_creation`, `data_table_refresh`, `data_table_settings_update`, `field_creation`, `quota_creation`, `quota_sync`, `quota_refresh`, `quota_settings_update`. Overall completion 0–100, averaged across the import's batches (100 when it has no batch). True when a batch you can read ended in error; get\_import says which and why. True when a batch you can read finished but rejected or skipped some rows; get\_import lists them. A run can carry a warning without an error. Distinct resource types the import touched — any of 'data\_table', 'quota', 'user'. The user who triggered the import; null for a system or scheduled import. Per-batch breakdown of the import — one entry per resource batch, each with its own status, progress, record counters and error state. Only the batches of resource types you can read are listed. How the data reached Qobra for this batch: 'integration' (a CRM sync), 'api', 'csv' (a file upload) or 'bulk\_action'. One of: `integration`, `api`, `csv`, `references`, `provisioning`, `bulk_action`. What this batch imported into: 'data\_table', 'quota' or 'user'. One of: `data_table`, `quota`, `user`. ObjectId of the imported resource (the data table or quota); null for a company-wide user import. The connection this batch synced from — one run can bundle several. Null for a CSV upload, a bulk action, an API push tied to no connection, or a connection since removed. One of: `kombo`, `salesforce`, `pipedrive`, `microsoft_dynamics`, `hubspot`, `odoo`, `zoho`, `attio`, `postgresql`, `gcp-postgresql`, `snowflake`, `snowflake-keys`, `redshift`, `microsoft-azure-sql`, `microsoft-azure-synapse`, `bigquery`, `s3`, `sftp`, `netsuite`, `public_api`. Batch status: 'scheduled', 'started' or 'finished'. One of: `scheduled`, `started`, `finished`. Completion of this batch, from 0 to 100. True when this batch ended in error. Why the connection failed, when the error came from the integration: 'authentication' (credentials rejected, reconnect it), 'permission' (the connected account lacks access), 'configuration' (a setting, query or object the customer set up is wrong or missing), 'rate\_limit' (too many requests, retry later), 'unavailable' (the integration is down or answered unreadably), 'invalid\_request' (the integration rejected the request itself) or 'unknown'. Null for an error raised inside Qobra, or one recorded before categories existed. One of: `authentication`, `permission`, `configuration`, `rate_limit`, `unavailable`, `invalid_request`, `unknown`. True when this batch finished but rejected or skipped some rows, listed in rejected\_rows. A batch can carry a warning without an error. What the batch did within its resource\_type. A user import names the part of the users it imported: 'users-attributes' (the users and their attributes), 'hierarchy' (who manages whom) or 'groups' (group memberships). A bulk action names the action it applied — on users: assign\_manager, assign\_licence, change\_role, change\_currency, change\_calculation\_currency, set\_attribute, assign\_group, revoke\_access, send\_invitation; on data-table records: edit\_records, reset\_records, activate\_records, deactivate\_records, delete\_records, change\_name\_field. Null for any other batch. How many rejections the import kept for this batch: one per rejected row, except a record\_range, which stands for a whole chunk of records. It keeps the last 100 only, so when rejected\_rows\_truncated is true there were more. For the number of records a batch did not apply, read number\_of\_records\_skipped. True when the batch reached the 100 rejected lines the import keeps: it rejected at least rejected\_row\_count rows, possibly many more. The first 20 rows the import kept, in the order it recorded them, each with its reason. When rejected\_rows\_truncated is true, the import had already dropped older rows, so these are not the batch's first rejections. The row's own source values are never included; the import's resource\_url opens the list the web app shows. What was rejected, by kind. 'line': a line of a CSV file or a record of an API payload, by position. 'user': a user batch's attribute error, or a user a bulk action failed on. 'group': a group a bulk action failed on. 'record': a record a data-table bulk action failed on. 'record\_range': a whole chunk of records a data-table bulk action failed on at once. Null when the import recorded nothing to locate the row by (a quota row, whose source values are never served). Always `line`. Line of the CSV file, or position of the record in an API payload, counted from 1. Always `user`. Id of the user the rejection is about, always set. That user, or null when no user has that id any more — typically a bulk action reporting it not found. Key of the user field an attribute error is on. Null when the rejection is about the user as a whole. Always `group`. The group a bulk action failed on. Always `record`. Id of the record a data-table bulk action failed on, in the table the batch's resource\_id names. Always `record_range`. Id of the first record of the chunk that failed. Id of the last record of that chunk: the failure covers every selected record between the two, in the table the batch's resource\_id names. Why the row was rejected or skipped, as the import recorded it, cut at 500 characters (ending with '…' when cut). Null when it recorded none. Total records processed by this batch, when tracked. # get_plan Source: https://docs.qobra.co/mcp_documentation/tools/get_plan Get the design view of one compensation plan: its compensations with periodicity, date range and the reporting metrics each feeds, plus its members. Get the design (conception) view of one compensation plan. Returns the plan with its compensations (their periodicity, date range and the reporting metrics each one feeds) and its attributed members. This is the plan-structure counterpart to get\_statement\_compensation, which returns the *calculated* values on an existing statement. A plan you can't access reads exactly like one that doesn't exist, so the error never reveals anything outside your scope — a plan living in a sandbox you may not read included. ## Parameters Id of the plan to open — the id list\_plans returns. ## Response Deep link to open this plan in the Qobra web app. Folder/group this plan is filed under (a concept shared with reports); null if unfiled. How many compensations the plan holds. Open the plan with get\_plan for each compensation's periodicity, date range and the reporting metrics it feeds, plus the plan's members. The rest of the calculation behind a compensation is not exposed there — read a statement's computed breakdown with get\_statement\_compensation. The plan's compensations with their configuration. True when the compensation has no fixed date range (it applies every period). YYYY-MM; null when the compensation is recurring. YYYY-MM; null when the compensation is recurring. "live" when the compensation is active on the plan; "draft" while it is still being edited and not yet applied. One of: `live`, `draft`. The reporting metrics this compensation feeds, one entry per metric flagged on one of its variables. Empty when this compensation feeds none — which says nothing about the plan on its own: a plan is missing from a report built on a metric only when none of its compensations feeds that metric. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. The users and groups attributed to the plan. The attributed user; null for group attributions. The attributed group; null for user attributions. # get_report Source: https://docs.qobra.co/mcp_documentation/tools/get_report Read one report's configuration: tracked data table, columns and their types, filters, sort, aggregates, and pivot or chart layout when set. Return one report's full detail: the list summary plus its configuration. The natural follow-up to `list_reports`: take a report and read how it is built — its tracked source datatable for record reports, its columns and their types, the filters and sort applied, the bottom-of-column aggregates, and the pivot-table / chart layout when configured. This is the configuration that makes the report's data interpretable; the computed rows and pivot cells live behind `get_report_data` and `get_report_pivot_data`, while the values displayed by a configured chart live behind `get_report_chart_data`. `access` (who can view the report) is only returned to callers with write access on reports; read-only callers get `access: null`. ## Parameters Id of the report to read — the id list\_reports returns. ## Response Source data table tracked by a record report, with its id and name; null for statement reports. The dashboard tiles this report can render: `chart` when a chart is configured on it, `pivot_table` when a pivot table is. Empty when neither is — the report then has no tile to show on a dashboard. One of: `read`, `write`, `delete`. One of: `read`, `write`, `delete`. One of: `read`, `write`, `delete`. One of: `AND`, `OR`. # get_report_chart_data Source: https://docs.qobra.co/mcp_documentation/tools/get_report_chart_data Return every value a report's configured chart displays: categories and series, donut slices, or bubble points, computed in a single call. Return every value produced by a report's configured chart. The chart-focused sibling of `get_report_data` and `get_report_pivot_data`: use this after `get_report` when you need the values actually displayed by the chart — categories, series, slices or points — rather than reconstructing them from raw rows or pivot cells. Bar, line and combo charts return categories and series; donut charts return a metric and slices; bubble charts return points. Pivot-backed charts use their relevant aggregates and subtotals, while raw-backed charts apply the chart's summing and percentage-normalization rules. A date-typed category (bar/line/combo `categories`, donut `slices`, bubble `points`) is returned as a pre-formatted period label, not a raw ISO date. The label reads exactly like the front-end chart axis, on the company's fiscal calendar, and in the caller's language: `Dec 2026` for a monthly axis, `Q1 2026` for a quarter, `H1 2026` for a semester, `2026` for a year (`Janv. 2020` / `T1 2026` / `S1 2026` in French). Chart axes never show a day. Series values remain numeric. The complete chart payload is returned in one call, with no pagination. A missing chart configuration, excessive cardinality, or a report too large to calculate raises a clear error. You only see reports you have access to — those granted to you directly, plus any rendered as a chart on a dashboard shared with you. ## Parameters Id of the report to read — the id list\_reports returns. ## Response The response shape depends on the chart's type. `chart_type` is the discriminator; `data_source` reflects the underlying report configuration (`raw`, `chart` or `pivot_table`); `autosummed` is `true` when the chart summed values automatically to produce these totals. ### Bar, line and combo charts Deep link to open this report in the Qobra web app. One of: `bar`, `line`, `combo`. One of: `raw`, `chart`, `pivot_table`. The x-axis values, in the order the chart lays them out. One entry per plotted series. `values` is aligned to `categories` (same length, same order); `tooltip_values` mirrors it when the chart shows a secondary tooltip metric. One of: `bar`, `line`. The breakout category this series is split by, or `null` when the series isn't broken out. ### Donut charts Deep link to open this report in the Qobra web app. Always `donut`. One of: `raw`, `chart`, `pivot_table`. The field the donut is measuring. One entry per donut slice. ### Bubble charts Deep link to open this report in the Qobra web app. Always `scatter`. One of: `raw`, `chart`, `pivot_table`. One entry per plotted bubble. `value` sizes the bubble and is `null` when the chart doesn't use a size metric. # get_report_data Source: https://docs.qobra.co/mcp_documentation/tools/get_report_data Read a report's computed rows: displayed columns, row values, and bottom-of-column aggregates, respecting filters, sort, and visibility. Return a report's computed rows — its actual data, filtered and sorted. The follow-up to `get_report`: once you know how a report is built, read its data. Returns the displayed columns, the row values (aligned to the columns), and the bottom-of-column aggregates, respecting the report's filters, sort and the caller's visibility. Use `get_report` first to read the field ids, types and filter/sort configuration that make these rows interpretable; use `get_report_pivot_data` for a report shown as a pivot table. Date values in `rows` are pre-formatted strings, not raw ISO dates. Each date column is rendered exactly the way the report displays it on screen, on the company's fiscal calendar, and in the caller's language: the statement period column shows a month (`Dec 2026` in English, `Déc. 2026` in French), and every other date column shows a day (`Dec 23, 2026` / `23 déc. 2026`). Do not try to parse these values as dates — treat them as display labels. `bottom_aggregates` still return raw values, so a `min`/`max` over a date column is a parseable ISO date. Paginated like every list tool: `limit` rows per call, `offset` to walk the pages (`next_offset` is `null` on the last one), `total_row_count` for the full size. Only the report's displayed columns are ever returned. A report too large to compute on the fly raises a clear error asking you to narrow it with filters. You only see reports you have access to. ## Parameters Id of the report to read — the id list\_reports returns. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. Restrict the returned columns to these field ids (as returned by get\_report). Omit to return all of the report's displayed columns. Hidden or unknown field ids are rejected. ## Response One of: `number`, `boolean`, `date`, `string`. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. One entry per row, aligned to `columns`. Values for `date` columns are pre-formatted period labels (see the note above), not raw dates. One of: `empty`, `not_empty`, `distinct`, `count`, `sum`, `average`, `min`, `max`. One of: `number`, `boolean`, `date`, `string`. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. # get_report_pivot_data Source: https://docs.qobra.co/mcp_documentation/tools/get_report_pivot_data Read a report's computed pivot-table cells: row and column groups, measures, subtotals, and paginated cell values respecting your visibility. Return a report's computed pivot-table cells — its cross-tabulated values. For a report configured as a pivot table (see `get_report`'s `pivot_configuration`): returns the row groups, the column groups, the measures, and the flattened cells (one per row-path × column-path × measure), respecting the caller's visibility. `__total__` in a path marks a subtotal or grand-total. For a flat (non-pivot) report, use `get_report_data` instead — calling this on a report with no pivot configuration raises a clear error. Grouped date values in `row_path` and `column_path` are pre-formatted period labels, not raw ISO dates. Each grouped date field is labelled by the period it covers on the company's fiscal calendar, at the group's own granularity — `Dec 2026` for a monthly group, `Q1 2026` for a quarter, `H1 2026` for a semester, `2026` for a year — in the caller's language (`Janv. 2020` / `T1 2026` / `S1 2026` in French). A pivot cannot group dates by day. Measure values in `cells` are unaffected: a date-typed measure keeps its raw ISO value. Paginated like every list tool: `limit` caps the number of cells per call, `offset` walks the pages (`next_offset` is `null` on the last one), `total_cell_count` is the full size. A page holds whole pivot rows, so it may return slightly fewer cells than `limit` (never more), and always at least one row. A pivot too large to compute on the fly raises an actionable error. You only see reports you have access to — those granted to you directly, plus any rendered as a pivot on a dashboard shared with you. ## Parameters Id of the report to read — the id list\_reports returns. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this report in the Qobra web app. The fields laid out on rows, outermost first. One of: `number`, `boolean`, `date`, `string`. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. The fields laid out on columns, outermost first. One of: `number`, `boolean`, `date`, `string`. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. One of: `number`, `boolean`, `date`, `string`. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. Value type of this measure's `__total__` cells, which can differ from `value_type` (for example a count of a string field yields a numeric total). Use this to interpret `__total__` rows and columns in `cells`. One of: `number`, `boolean`, `date`, `string`. Field type of this measure's `__total__` cells, which can differ from `field_type`. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. One cell per row-path × column-path × measure; `__total__` in a path marks a subtotal/grand-total position. Values are aligned to `row_groups`. Entries for grouped `date` fields are pre-formatted period labels (see the note above), not raw dates. Values are aligned to `column_groups`. Entries for grouped `date` fields are pre-formatted period labels (see the note above), not raw dates. Row offset for the next page — pass it back as `offset` to fetch the following cells. `null` on the last page. # get_request Source: https://docs.qobra.co/mcp_documentation/tools/get_request Read one request's full detail plus its chronological message thread: members, statements it touches, and every mention resolved by name. Return one request's full detail plus its message thread, chronologically. The natural follow-up to `list_statements`: take a `linked_requests` row and read the whole request — who is involved, which statements it touches, and the discussion. Every related entity is surfaced by name, never a bare id — inside a message too, where an @-mention reads as `@` and the people it names are listed in that message's `mentions`. A request you can't access is reported as not found. File attachments are omitted. ## Parameters Id of the request to read — the id list\_requests returns, or the one in a statement's linked\_requests. ## Response Deep link to open this request in the Qobra web app. The #-style reference shown in the UI. Admin-configured category used to classify the request; null if none. open (awaiting handling) or closed (resolved). One of: `open`, `closed`. The linked deal/record when the statement is record-based; null otherwise. The message as written, with every @-mention rendered as `@` instead of the raw user id the web app stores. See `mentions` for the ids and emails behind those names. The users @-mentioned in `content`, in order of first appearance; empty when the message mentions nobody. A mention whose id matches no user is left as a raw handle in `content` and is absent here. # get_role Source: https://docs.qobra.co/mcp_documentation/tools/get_role Return a single role with its granted permissions in plain language and the resources shared to it, so you can explain what a user may see and do — and why. Return a single role with the permissions behind it, in plain language. This is how you explain what someone can and cannot do, and why: reach for it when a user does not see something they expect, or to answer who is allowed to perform an action. Feed it the `id` from any user row's `role` (list\_users, get\_user, get\_identity), or from list\_roles. The permissions are rendered as *grants*, not as the raw matrix — a category the role is denied is left out, and inside an allowed one a yes-or-no permission the role does not hold is left out too. An absence is therefore meaningful: a category missing from the result is one the role cannot reach, which is often the answer to "why can't this user see X". The org-chart `scope` on statements, requests and commission letters is what decides *whose* records the role sees, independently of those grants. `shared_resources` is the other half of the picture: resources shared with the role individually, which reach past its category permissions. A role denied a whole category can still read specific resources through a share, so read the two together. Sharing is a live-environment concept, so those are the live ones. This tool needs the company-settings *roles* permission (READ on the Role company setting). ## Parameters Id of the role to look up — the id list\_roles returns, or the one on the `role` of any user row. ## Response Deep link to open this role in the Qobra web app. Whether this is the role newly-created users get unless another one is picked. Exactly one role of a company is the default. Whether this is the company's built-in Admin role, which holds every permission and cannot be edited. Number of non-archived users currently holding this role. The role's permissions, one entry per category it is allowed at all. A denied category is omitted entirely, so a category absent from this list is one the role cannot reach — that absence is the answer to "why can't this user see X". Coded permission category — one section of the role's matrix, matching a section of the Qobra app. One of: `statement`, `request`, `report`, `commission_letter`, `plan`, `quota`, `user`, `data_table`, `company_setting`, `audit_trail`, `sandbox`. The category's label as it reads in the web app. How far the category reaches across the org chart, for the categories that have such a setting (statements, requests, commission letters): `all` every user's, `users_below` the holder's own plus their reports', `me` their own only, `no_access` none at all — the category page opens but shows nothing. Null for a category with no org-chart scope. One of: `all`, `users_below`, `me`, `no_access`. What the role is granted inside this category. Only grants are listed: a yes-or-no permission the role does not hold is absent, so an empty list on a category that has sub-permissions means access to the page and nothing more. The request category has no sub-permissions at all — its scope is the whole grant. Coded sub-permission. A value repeats across categories with a different meaning (`role` is the Roles settings page under `company_setting`, a user's assigned role under `user`), so read it together with the category it sits in. The sub-permission's label as it reads in the web app's role settings. How far the granted permission goes. `view` (read), `edit` (write), `full` (write plus delete). One of: `view`, `edit`, `full`. What the role may do, in the web app's own wording — 'Can view' / 'Can edit' / 'Can edit and delete' for a graded permission, an action phrase such as 'Can lock' for a yes-or-no one. Resources shared with this role individually, on top of what `permissions` grants by category. This is the other half of the answer to 'why can this user see X': a role denied a category can still reach specific resources through a share. Empty when nothing is shared with the role. Live environment only. The shared resource itself — a saved view, or the report or dashboard that was shared. What kind of resource is shared: `statement`, `request`, `quota` or `data_table` for a saved view of that resource, `report` or `dashboard` for the object itself. The resource the view filters, when it belongs to one — the data table or quota it is a view of. Null for a resource that stands alone. The level this share grants the role on this resource specifically — `read` (view it), `write` (edit and re-share it) or `delete` (also remove it), each level including the ones before it. One of: `read`, `write`, `delete`. # get_sales_coach_discussion Source: https://docs.qobra.co/mcp_documentation/tools/get_sales_coach_discussion Read the full transcript of one Sales Coach discussion: each question, its answer, the Qobra data tools the agent ran, and any escalations. Return the full transcript of one Sales Coach discussion. A list\_sales\_coach\_discussions row plus every message in order — each question, the answer it got, the Qobra data tools the agent ran to ground that answer — and the escalations that opened a request. Reach for it to diagnose a single conversation: what was asked, what the agent answered, and where it fell back to a human. Reserved to full statement access, like list\_sales\_coach\_discussions. A discussion you cannot reach reads exactly like one that does not exist. ## Parameters Id of the discussion to read — the id returned by list\_sales\_coach\_discussions. ## Response Deep link to the statement the discussion is about, in the Qobra web app. Discussions have no page of their own. The user who asked the questions. The statement a discussion is about — the subject every question is asked against. Statement period, YYYY-MM. The user the statement belongs to. Usually also the discussion's author (a rep asking about their own statement), but an admin or manager can question someone else's statement. The opening question, verbatim — what the user came for. Null for a discussion with no user message yet. Number of questions the user asked. Excludes the replies they gave to the agent's own clarifying questions, which are stored as user messages too. Total messages in the transcript, questions and answers together. Number of times the agent escalated to a human, opening a request. Above zero means the agent could not answer on its own — see the `escalations` array below for detail. When the discussion was opened. Last activity on the discussion. The full transcript in chronological order — each question and the answer it got, with the tools that produced it. Who wrote the message: 'user' (the question asked), 'assistant' (the agent's answer) or 'system'. Tool traffic is folded into the answer's `tools` rather than surfacing as its own message. One of: `user`, `assistant`, `system`, `tool`. Names of the Qobra data tools the agent ran to produce this answer, in call order — how it grounded the reply. Empty on a user message, and on an answer that needed no lookup. The human hand-overs the agent opened, in order. The request the escalation opened, named after the escalation title. Null when you have no access to that request — the escalation still happened. # get_statement Source: https://docs.qobra.co/mcp_documentation/tools/get_statement Audit one statement end to end: its commission lines, complete activity log, validation state, draw balance, and its linked requests. Get the full, audit-grade detail of one statement you can access. Use this to audit a single statement end-to-end — drill into the summary rows `list_statements` returns. It returns one minimal row per commission line (drill into a line's variables and scopes with `get_statement_compensation`), the complete activity log, the validation state, the draw balance, and the linked requests. Identify the statement by `statement_id` — the id `list_statements` returns. The headline `total_amount` is converted into the currency named by `currency_mode` (payment by default) and labelled with it, so amount and currency always agree. The `conversion` block reports what that took — the currency the statement stores its amounts in and the rate applied — so a converted figure can be justified without guessing; `list_exchange_rates` holds the company's rates per period. A company with no rate configured for the requested currency is refused rather than served in another one. The per-line compensation breakdown stays in the plan's calculation currency. A statement you can't access reads exactly like one that doesn't exist, so the error never reveals a statement outside your scope. ## Parameters Id of the statement to open — the id list\_statements returns. Which currency total\_amount is converted into and labelled with: 'payment' (default) — the currency the user is paid in; 'calculation' — the currency the plan computes in; 'company' — the company's global currency. The amount and its currency always agree. One of: `company`, `payment`, `calculation`. ## Response Deep link to open this statement in the Qobra web app. Overall processing state: error (calculation failed), ongoing (open, not yet locked), locked (frozen, not yet paid), paid (sent to payroll). A paid statement is always frozen too. Distinct from the per-period reporting stage of list\_statement\_periods. One of: `error`, `ongoing`, `locked`, `paid`. Net amount to be paid out on this statement, expressed in `currency` — already reflects draw and manual payments, so it is not just the sum of the compensation lines. The ISO currency code that total\_amount is expressed in. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. When the statement last finished computing and was up to date; null if it never reached that state. True if at least one amount was manually adjusted. The groups (by dimension) this user belongs to on the statement. Deep link to open this group in the Qobra web app. The group's stable key — the identifier used to match it when synced from an integration, distinct from its display name. The dimension the group belongs to (the kind of grouping, e.g. 'Country' or 'Team'). Null if the dimension is unknown. Number of users currently in the group (attributions active as of today). Past and future members are excluded. Opening balance carried over from the previous period. Balance set manually (overwrite); null if not overridden. The draw applied this period. True when the balance was entered or edited by hand. Balance once this period's draw is applied. Workflow step this event records: freeze (statement locked), validation (an intermediate, informational sign-off), payment (sent to payroll). One of: `freeze`, `validation`, `payment`. One minimal row per commission line. Drill into a line with get\_statement\_compensation for its variables and scopes. Deep link that opens this line in the Qobra web app — its statement, with this compensation selected. The two aggregated lines (manual payments, draw) carry no `compensation_id`; the statement selects each by its own flag instead. Identifier to pass to get\_statement\_compensation for the full breakdown (variables, scopes). Null for a manual payment or draw that has no underlying compensation. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. The comment the actor wrote, with every @-mention rendered as `@` instead of the raw user id the web app stores; null when the activity carries no comment. See `comment_mentions` for the ids and emails behind those names. The users @-mentioned in `comment`, in order of first appearance; empty when it mentions nobody. A mention whose id matches no user is left as a raw handle in `comment` and is absent here. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. The statement's detailed calculation errors, resolved to variable / quota / scope names. Populated only for callers who can manage compensations or quotas (mirroring the web app's admin-only error panel); empty for anyone else and whenever the statement has no calculation error. The compensation the error sits on — feed its id to get\_statement\_compensation for the variable breakdown behind it. The variable whose computation errored. The scope the errored variable belongs to, when the error is on a scope record rather than a compensation variable. The kind of calculation error: * FormulaError / DivisionZeroError: the variable's formula could not be computed. * TypeError: a value had an unexpected type. * ConversionError: no exchange rate is configured for a currency in the period. * NullQuotaError / NullQuotaProjectionError: a quota has no value for the period. * ScopeTooLargeError: a scope returned too many records to compute. * FieldDependencyError / FieldReferenceError / DependencyError: the variable depends on another field/variable that is missing or itself in error. One of: `FormulaError`, `DivisionZeroError`, `TypeError`, `ConversionError`, `NullQuotaError`, `NullQuotaProjectionError`, `ScopeTooLargeError`, `FieldDependencyError`, `FieldReferenceError`, `DependencyError`. A human-readable specifics line for the error — the missing currency, quota, scope or dependency by name — or null when the error type carries no extra detail. How every amount on this statement reached `currency` — the currency it was stored in and the rate applied. The currency the statement stores its amounts in — its calculation currency, the one its plan computes in. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. The exchange rate applied to every amount here: an amount in `source_currency` times this rate gives the amount in `currency`. 1 when both are the same currency, i.e. nothing was converted. Read list\_exchange\_rates for the company's rates and the periods they apply to. # get_statement_compensation Source: https://docs.qobra.co/mcp_documentation/tools/get_statement_compensation Get the calculated breakdown of one commission line on a statement: every variable value, admin overrides, plan, periodicity, and scopes. Get the full calculated breakdown of one compensation on a statement. A compensation is one commission line of a statement. `get_statement` returns each line as a minimal row; this returns the *whole* displayable variable breakdown behind its amount — every variable with its `value` (and, when an admin overrode it, the `value_before_override`) — plus the compensation's plan, periodicity, ranking and its scopes (drill into their frozen records with `list_statement_records`). Hidden intermediate variables and the calculation rules themselves (formulas, tiers) are not exposed. Identify the statement by `statement_id` (from `list_statements` / `get_statement`), then the line within it by `compensation_id`. A statement or compensation you can't access reads exactly like one that doesn't exist, so the error never reveals anything outside your scope. ## Parameters Id of the statement the compensation sits on — the id list\_statements or get\_statement returns. Id of the compensation to open, from the `compensation_id` of the line in get\_statement's compensations. ## Response Deep link that opens this line in the Qobra web app — its statement, with this compensation selected. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. True when the compensation or its line errored. How often the compensation is calculated, on the company's fiscal calendar. EACH\_MONTH every month; EACH\_MONTH\_EXCEPT\_END\_OF\_QUARTER every month but the one each quarter closes on; EACH\_QUARTER, EACH\_SEMESTER and EACH\_YEAR at each period's close; Q1-Q4 once a year, at that quarter's close only. One of: `EACH_MONTH`, `EACH_MONTH_EXCEPT_END_OF_QUARTER`, `EACH_QUARTER`, `Q1`, `Q2`, `Q3`, `Q4`, `EACH_YEAR`, `EACH_SEMESTER`. The compensation's scopes (its calculation perimeters), those the statement displays — a scope the plan hides is excluded, as hidden variables are. Drill into a scope's frozen records with list\_statement\_records. Deep link that opens this scope in the Qobra web app — its statement, on its compensation line, scrolled to this tab. The scope's short label within the compensation (A, B, …). Every displayable variable of the compensation with its value — the full calculated breakdown behind the amount. Hidden (intermediate) variables are excluded. One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. The variable's value (final, after any manual override). True when an admin manually overrode the computed value. The computed value before the manual override. Null unless `overwritten` is true. Set only for AMOUNT variables — the statement's calculation currency; null otherwise. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. When `error` is true, the kind of computation error (e.g. DivisionZeroError, DependencyError, TypeError); null otherwise. # get_user Source: https://docs.qobra.co/mcp_documentation/tools/get_user Return one user's full profile: identity, integrations, managers, group and plan attributions, custom attributes, and membership windows. Return the full profile of a single user in your company. Beyond identity (name, email, role, currencies, status), the result joins in the user's integrations (with the connector's human name), managers, group attributions, compensation-plan attributions and custom user attributes. Membership windows (manager / group / plan) are `YYYY-MM` month strings; a null end / departure date means the assignment is still ongoing. Each attribute is rendered human-readable — amounts carry their currency, dates are ISO-8601 instants in UTC, picklists render as labels and user references as `{id, email, name}` objects rather than raw ids — and an unset attribute surfaces as null. Each attribute here is *this user's* value. For the schema behind them — each attribute's type, whether it is synced from a source system, and the labels a picklist accepts — call [`list_user_attributes`](/mcp_documentation/tools/list_user_attributes). `shared_resources` is the half of "why can this user see X" that their role does not carry: views, reports and dashboards shared with them *individually*. A user whose role denies a whole category can still reach specific resources this way, so read it alongside [`get_role`](/mcp_documentation/tools/get_role) on their `role` id rather than concluding from the permissions alone. The rows follow the same shape `get_role` returns for a role's shares. Sharing is a live-environment concept, so those are the live ones. Reading someone else's shares is administrative information: rows for a source you are not allowed to read the sharing of are dropped, and a caller with no such permission on any source gets an empty list. Calling `get_user` on yourself always returns your own shares in full. ## Parameters Id of the user to look up — the id list\_users returns. An exact email is accepted too. ## Response Deep link to open this user in the Qobra web app. User lifecycle state: pending (invited, not logged in yet), active, inactive (deactivated), archived (has left the company; hidden by default in list\_users). One of: `inactive`, `pending`, `active`, `archived`. The user's permission role — what decides what they may see and do, not their status. Pass its id to get\_role to read the permissions behind it. The user's direct manager today; null when they report to no one. `managers` below lists their full manager history with dates. Currency the user is actually paid in. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. Currency the user's commissions are computed in. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. Whether the user holds a paid Qobra license (a seat). Only licensed users have their compensations calculated and a statement to view — an unlicensed user's statement is never computed, which is the usual reason someone 'can't see X's statement'. A newly-arrived user typically needs a license added; a departed user who no longer appears in the data is a candidate for archiving to free their seat. True when the user is NOT licensed yet has compensations that would otherwise be calculated — the statement can't be computed for lack of a seat. This is what raises the red warning banner at the top of a statement; resolve it by assigning the user a license. The user's links to the company's connected integrations (HRIS, CRM…), one entry per integration they are matched in. An empty list means the user is matched in none — which is what 'this user is missing from the \ sync' looks like. Use list\_users to answer sync-coverage questions over the whole company in one call; never call get\_user per user. The company connection this link belongs to. The user's id in the customer's own system. When this user's identity was last refreshed from the HRIS integration. Null means never synced — either the company has no HRIS connected, or this user has never been matched in it. YYYY-MM; the month the management started. YYYY-MM; null while the management is still ongoing. The dimension the group belongs to; null if unknown. YYYY-MM; the month the user joined the group. YYYY-MM; null while the membership is still ongoing. YYYY-MM; the month the user joined the plan. YYYY-MM; null while the attribution is still ongoing. Id of the attribute this value belongs to — the id [`list_user_attributes`](/mcp_documentation/tools/list_user_attributes) returns, so a value maps back to its definition even when two attributes share a display name. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. Resources shared with this user *individually*, on top of whatever their role grants — call [`get_role`](/mcp_documentation/tools/get_role) with their `role` id for the other channel, which covers everyone holding that role. Read the two together: this is what makes someone see a view their role alone would deny, so a permission answer built on the role only can be wrong. Empty when nothing is shared with them individually — or, on a user other than yourself, when nothing shared with them belongs to a resource you may administer the sharing of. Same shape as [`get_role`](/mcp_documentation/tools/get_role)'s `shared_resources`. Live environment only. The shared resource itself — a saved view, or the report or dashboard that was shared. What kind of resource is shared: `statement`, `request`, `quota` or `data_table` for a saved view of that resource, `report` or `dashboard` for the object itself. The resource the view filters, when it belongs to one — the data table or quota it is a view of. Null for a resource that stands alone. The level this share grants on this resource specifically — `read` (view it), `write` (edit and re-share it) or `delete` (also remove it), each level including the ones before it. One of: `read`, `write`, `delete`. # list_audit_logs Source: https://docs.qobra.co/mcp_documentation/tools/list_audit_logs List your company's audit-trail entries, newest first: data edits, rule changes, statement adjustments, and exports, with actor filters. List your company's audit-trail entries, most recent first. The audit trail records every action taken on the platform — data edits, compensation-rule changes, statement adjustments, exports — one entry per action, each with who did it, when, and on which resource. Combine the filters (AND) to zoom in: actor\_id='\' for one person's actions, or resource\_id=\ + after='-P1M' for a resource's recent history. Fetch get\_audit\_log for the per-field change payload of a single entry. Only the last 12 months are queryable — older entries predate the current change-tracking and are not returned. ## Parameters Keep only logs about the resource with this exact id. Omit for every resource. Keep only actions performed by the user with this id — the id list\_users returns, or their exact email. Omit for every actor. Keep only logs with this exact event name, e.g. 'RecordUpdated' — the coded action that happened. Omit for every event. Keep only logs at or after this instant. Accepts an absolute ISO-8601 date/time ('2026-07-01') or a duration relative to now ('-P7D' for the last 7 days). Omit for no lower bound. Keep only logs at or before this instant. Same accepted forms as after. Omit for no upper bound. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link that opens this audit-log entry's detail modal in the Qobra web app. When the action happened (UTC, ISO-8601). Coded name of the audited event, e.g. 'UserUpdated' or 'CompensationRuleUpdated' — the precise action taken. Id of the affected resource; null when not applicable. Human-readable name of the affected resource, if known. ObjectId of the actor; feed it to get\_user for detail. Actor's email as recorded at action time — the identifier to recognize the user by. Null for a system/script action. Actor's role name as recorded at action time. The surface the action was performed through: `Qobra UI` is the web app, its in-app AI agents (the Analyst, the Architect) included; `Qobra MCP` is an MCP client acting as the actor; `Qobra Onboarding` is the self-serve onboarding agent. Null on entries written before the surface was recorded: unknown, not the web app. One of: `Qobra UI`, `Qobra MCP`, `Qobra Onboarding`. Deep link that reopens this exact search in the Qobra web app's audit trail — the applied filters (resource, actor, event, date range) encoded as query params. Hand it to the user to let them browse the same results in the UI. # list_commission_letters Source: https://docs.qobra.co/mcp_documentation/tools/list_commission_letters List a commission-letter campaign's letters page by page, one row per recipient with envelope status, signers, pre/post signers and CC. List the letters of one commission-letter campaign, one row per recipient. The per-recipient side of `get_commission_letters_campaign`: each row carries the letter's e-signature envelope (its status and when it reached it, the ordered signers), the pre/post signers the campaign sets for that recipient and the people in CC. Every person is surfaced by email, never a bare id. Rows follow the campaign's recipient order. A campaign you can't access is reported as not found. ## Parameters Id of the campaign whose letters to list — the id list\_commission\_letters\_campaigns returns. Keep only the letters in this signature state: awaiting\_signature (sent, waiting\_for\_approval, received or viewed: a signer still has to act), signed, or action\_needed (declined, voided or expired). A letter whose e-signature envelope is still being created counts as awaiting\_signature; one of a draft or e-mail campaign matches none. Omit for every letter. One of: `awaiting_signature`, `signed`, `action_needed`. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Pass the next\_offset returned by the previous call, or None for the first page. ## Response The user this letter is for. E-signature state of this letter: sent, waiting\_for\_approval, received, viewed, signed, declined, voided, expired. Null when the letter has no envelope yet: the campaign is still a draft, was sent by e-mail, or its e-signature envelope is still being created. One of: `sent`, `waiting_for_approval`, `received`, `viewed`, `signed`, `voided`, `expired`, `declined`. When the envelope reached its current status. The ordered signers of this letter's envelope; empty without an envelope. Signer state: sent, received, delivered (means the letter was viewed), completed (signed), resent (reminder sent), declined. One of: `sent`, `received`, `delivered`, `completed`, `resent`, `declined`. When this signer reached their current status. Who the campaign has sign this letter before its owner. Who the campaign has sign this letter after its owner. People in copy of this letter. For email they are copied when the letter is sent; for DocuSign they are notified after all signers complete. They do not sign. # list_commission_letters_campaigns Source: https://docs.qobra.co/mcp_documentation/tools/list_commission_letters_campaigns List commission-letter campaigns as summaries with a derived overall status and per-envelope letter counts for e-signature and e-mail sends. List your company's commission-letter campaigns as ready-to-judge summaries. A campaign is one batch send of commission letters, built from a template for a set of recipients. Each row is a summary — never per-recipient detail — carrying everything needed to answer "where is this campaign?" without recomputing anything: a derived `overall_status` and `envelope_counts` (letters tallied per e-signature envelope status, already aggregated). Campaigns sent by e-mail have no envelopes, so their `envelope_counts` is empty. Rows are ordered most-recently-created first. You see every campaign in the company. ## Parameters Free-text filter, matched case-insensitively as a substring of the campaign name or of its ObjectId. The campaign's overall status. One of draft (not sent), generating (letters still being generated), sent (sent by e-mail), awaiting\_signatures (e-signature, some letters still pending), completed (every letter signed) or action\_needed (at least one letter declined, voided or expired). One of: `draft`, `generating`, `sent`, `awaiting_signatures`, `completed`, `action_needed`. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this campaign in the Qobra web app. How the letters are delivered: email (plain email) or esignature (electronic signature). One of: `email`, `esignature`. One of: `draft`, `generating`, `sent`, `awaiting_signatures`, `completed`, `action_needed`. Letters tallied per e-signature envelope status; empty for e-mail campaigns (they have no envelopes). # list_dashboards Source: https://docs.qobra.co/mcp_documentation/tools/list_dashboards List your company's reporting dashboards with name search and pagination, scoped to the dashboards shared with you or your full dashboard access. List your company's reporting dashboards, with an optional name filter and pagination. Each row summarizes one dashboard: its name, description, and when it was last updated. Dashboards live under the reports section; a dashboard groups visualizations of existing reports — call `get_dashboard` for a dashboard's tiles, their layout, and the reports they render. You only see the dashboards you have access to — every one of the company's at full dashboard access, otherwise only those shared with you (by role or individually); the rest are omitted. ## Parameters Filter matched against the dashboard name (case- and accent-insensitive substring) or, when it is a valid ObjectId, against the dashboard id. Id of the sandbox to read instead of the live environment — the id list\_sandboxes returns. Omit it to read the live environment, the company's real compensation data, which is the right choice unless the question is explicitly about a sandbox. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this dashboard in the Qobra web app. # list_data_table_records Source: https://docs.qobra.co/mcp_documentation/tools/list_data_table_records List one data table's records with human-readable cell values plus metadata: name, source, external id, tag, active flag, and timestamps. List one data table's records, each column rendered human-readable. A data table is a company object table (deals, opportunities, accounts, custom objects…); a record is one row. Each record carries its id, its cell `values`, and record metadata: `name` (the table's name column), `source` (origin), `source_id` (external system id), `tag` (status), `active`, `created_at` and `last_modified_at`. Cell values never expose raw ids — amounts carry their currency, dates are ISO-8601 instants in UTC, picklists resolve to labels, user references to a structured `{id, email, name}` object and linked records to their name. **A record's `values` are positional against `data_table.fields`** — the Nth value is the Nth field, so read that header to know a cell's name and type. The column's name and type are stated once there rather than repeated on every cell, which keeps a page of a wide table small. You only see tables and records you have access to; a table you can't access is reported as not found. When a table reaches you through several shared views, pass `view_id` to pick which one. Rows are ordered most-recently-created first for local tables; for integration-synced tables they are ordered alphabetically by record name. The result carries a list-level `application_url` that reopens the exact list you just fetched — the table's page with the chosen view selected. It is only set when a view was passed; otherwise it is null and the table's own `resource_url` already opens the page. ## Parameters Id of the table to read — the id list\_data\_tables returns for a customer-facing (local or integration) table. Free-text filter, matched the same way as the records page — a case- and accent-insensitive substring of the record's name and external id. Omitted returns the table's records unfiltered. AND-ed with the applied view's row filter. Id of the view to narrow the records to, from the `views` of the table's row in list\_data\_tables. Required only when several shared views reach the table. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this data table in the Qobra web app. One of: `kombo`, `salesforce`, `pipedrive`, `microsoft_dynamics`, `hubspot`, `odoo`, `zoho`, `attio`, `postgresql`, `gcp-postgresql`, `snowflake`, `snowflake-keys`, `redshift`, `microsoft-azure-sql`, `microsoft-azure-synapse`, `bigquery`, `s3`, `sftp`, `netsuite`, `public_api`. The views the caller may query for this table's records — pass one's id as `view_id` to list\_data\_table\_records to narrow the rows. Empty when no view is relevant. Stable technical key for this field in Qobra's public API (use it as a durable reference; the display name can change). Field data type: amount, percentage, float, string, bool, date, picklist, multipicklist, user, or object (a reference to another data table — see linked\_to). One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. When type is object, the table this field points to; null otherwise. Name of the linked table. `null` when the caller has no access to the linked table (for example, when the link is visible only through a shared view on the current table). `id` is always returned. Whether create\_record / update\_record can set this column. False for a lookup, whose value is read from the linked record, and for a link to another table. Deep link that opens this record's detail in the Qobra web app — its table's page, focused on this row. When a `view_id` was passed, the link carries it too so the record opens against that same view. Where the record comes from: integration (synced from a connected system) or local (created in Qobra). One of: `integration`, `local`, `qobra`, `reporting`. The record's id in its source system. Integration record status (Created / Overwritten / Archived). Null for local-table records (no integration baseline) and for untouched integration rows. The record's cell values, one per column, in the same order as the enclosing result's data\_table.fields — read the Nth value against the Nth field to know its name and type. A cell carries only what varies per row (the value, plus its currency for an amount); the column's name and type are stated once in that header rather than repeated on every cell. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. Deep link that reproduces this list in the web app — the table's page with the chosen view selected. Null when no view filter was applied (the table's own resource\_url already opens the page). # list_data_tables Source: https://docs.qobra.co/mcp_documentation/tools/list_data_tables List your company's local and integration-synced data tables with their fields, public-API keys, data types, and linked-table references. List your company's data tables and their fields, with filters and pagination. Only local and integration-synced tables are returned. Each table carries its source, its public-API key, the integration it syncs from for integration tables, and its fields. Every field carries its public-API key in `.` form, its data type and — for a link field pointing at another table — that table's id and name. You only see the data tables you have access to. Rows are ordered alphabetically by table name (A→Z). ## Parameters Filter matched against the table name (case- and accent-insensitive substring) or, when it is a valid ObjectId, against the table id. Filter restricting results to local or integration tables. Omit to return both kinds. Keep only tables synced from the integration with this id — the id list\_integrations returns. Local tables have no integration, so this excludes them. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Pass the next\_offset returned by the previous call, or None for the first page. ## Response Deep link to open this data table in the Qobra web app. One of: `kombo`, `salesforce`, `pipedrive`, `microsoft_dynamics`, `hubspot`, `odoo`, `zoho`, `attio`, `postgresql`, `gcp-postgresql`, `snowflake`, `snowflake-keys`, `redshift`, `microsoft-azure-sql`, `microsoft-azure-synapse`, `bigquery`, `s3`, `sftp`, `netsuite`, `public_api`. The views the caller may query for this table's records — pass one's id as `view_id` to list\_data\_table\_records to narrow the rows. Empty when no view is relevant. Each view also carries an `access` roster — roles and users who can read it — populated when you may share this resource, `null` otherwise. Who can read this view, split into roles and users, each with the level they hold. Only the tools that list views for you to choose between resolve it — list\_quotas, list\_data\_tables and get\_identity — and only for callers who may share the resource. Null everywhere else, so null is silence about the roster (either not asked for, or not yours to see), never an empty one: a caller who may see it and finds nobody gets empty `roles` and `users` instead. The level this role holds on the whole resource category, which reaches every view of it without a share — `read` (view it), `write` (edit and re-share it) or `delete` (also remove it), each level including the ones before it. Null when the role only got this view through a share. One of: `read`, `write`, `delete`. The level granted to this role on this view specifically — `read` (view it), `write` (edit and re-share it) or `delete` (also remove it), each level including the ones before it. Null when the role reaches the view through its category access rather than a share. One of: `read`, `write`, `delete`. The level this view is shared with the user at, individually, on top of whatever their role already grants — `read` (view it), `write` (edit and re-share it) or `delete` (also remove it), each level including the ones before it. One of: `read`, `write`, `delete`. Stable technical key for this field in Qobra's public API (use it as a durable reference; the display name can change). Field data type: amount, percentage, float, string, bool, date, picklist, multipicklist, user, or object (a reference to another data table — see linked\_to). One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. When type is object, the table this field points to; null otherwise. Name of the linked table. `null` when the caller has no access to the linked table (for example, when the link is visible only through a shared view on the current table). `id` is always returned. Whether create\_record / update\_record can set this column. False for a lookup, whose value is read from the linked record, and for a link to another table. # list_dependencies Source: https://docs.qobra.co/mcp_documentation/tools/list_dependencies List everything that references a quota, data table, group, group dimension, plan, report or integration, so you know what breaks before you change it. Answer "what breaks if I change or delete this?" for one resource. The tool returns everything that references the resource — compensations and their plans, the plans a group is attributed to, reports, dashboards, saved views, variables, lookup fields, plan simulation pipelines, and the data tables and quotas an integration syncs — each as a uniform row naming the dependent, the object it lives inside, and the sandbox it belongs to (null on live). Reach for it before advising on a change to a quota, data table, group, group dimension, plan, report or integration, and to answer "is this still used?" — an integration whose `total` is 0 is the one the web app badges as unused. For an integration the answer covers its synced data tables and quotas, the same two kinds the web app's own dependencies dialog lists. It does not cover the users, user attributes and groups an HRIS connection syncs, which disconnecting would unsync too. So a `total` of 0 on an integration is not on its own a verdict that disconnecting it is safe. Not every dependent blocks a deletion. A group's plan attributions (`plan_attribution` rows) do not: deleting the group removes it from those plans instead, so its members leave them and their statements change. Check for these rows before deleting a group. One call returns every dependent — there is no pagination and no cap, because a partial impact analysis is worse than none. `counts_by_type` rolls the same rows up per nature so you can read the shape of the impact at a glance, and a `total` of 0 means nothing references the resource, so changing or deleting it breaks nothing. Dependents are reported in full even when you could not open one yourself: withholding one would read as "nothing uses this", which is the wrong conclusion to act on. Each dependent carries only its id and name, never its contents. ## Permissions Reading a resource's dependents needs **full read access** to that kind of resource — a resource merely shared with you does not admit you, since the question exists to be asked before changing or deleting it. The tool is visible as soon as your role can read *any* of the seven resource types, but each call is checked against the permission the `resource_type` argument selects: you can only analyze the kinds you may read. Analyzing an `integration` needs read access to company settings' integrations. Analyzing a `report` additionally requires the reporting feature to be enabled for your company. This tool is read-only: it says what *would* break and changes nothing. ## Parameters What kind of resource `resource_id` names. One of: `quota`, `data_table`, `group`, `group_dimension`, `plan`, `report`, `integration`. Each kind is read under its own permission, so you can analyze only the kinds you may read. Id of the resource to analyze — the id the matching list tool returns (list\_quotas, list\_data\_tables, list\_groups, list\_group\_dimensions, list\_plans, list\_reports, list\_integrations). An id, never a name. ## Response The kind of resource that was analyzed. One of: `quota`, `data_table`, `group`, `group_dimension`, `plan`, `report`, `integration`. The resource whose dependents were analyzed. Link to this resource's page in the Qobra web app, stamped with its sandbox when it lives in one. Null for a group dimension, which is managed inside the users page and has no page of its own. Total dependents across every nature — the length of `dependencies`. Zero means nothing references this resource, so changing or deleting it breaks nothing. Per-nature rollup of `dependencies`, to read the shape of the impact at a glance. Natures with no dependent are omitted. Dependents of this nature. A nature absent from the list has none. Every dependent, ordered by nature then name. The whole set, never a page and never capped — `total` is always its length. The nature of the dependency — how the dependent uses the analyzed resource. `compensation`: a commission rule reads it. `compensation_used_as_scope`: a compensation consumed as another one's scope. `dashboard` / `report`: a reporting object renders it. `table`: a data table matches users on it. `group`: a group belonging to the dimension, which blocks deleting it. `plan_attribution`: a plan the group is attributed to. It does not block deleting the group, but the deletion removes the group from that plan, so its members leave the plan and their statements change. `scope`: a compensation's scope. `data_table` / `quota`: a resource synced from the integration. `lookup_field`: a field looking it up on another table. `scope_variable` / `statement_variable` / `sync_variable`: a variable computing from it. `commission_letter`: a letter campaign. `object_view` / `quota_view` / `statement_view` / `request_view`: a saved view filtering on it. `public_api`: the public API matches users on it. `plan_simulation_pipelines`: a plan's simulation pipeline. `record_report_dependency`: a record report tracks it. The dependent object itself. Null for the two natures that name no object — `public_api` and `record_report_dependency`, where the dependency is the mechanism rather than a row you can open. The object the dependent lives inside, when its nature has one: the plan for a compensation or a simulation pipeline, the data table for an object view or a lookup field, the quota for a quota view. Null when the dependent stands alone. The sandbox this dependent lives in, or null when it lives in the live environment. A dependent in a sandbox does not affect live compensation. # list_exchange_rates Source: https://docs.qobra.co/mcp_documentation/tools/list_exchange_rates Read the company's exchange rates per date range: what currency was applied, at what rate, and since when — the source of every converted amount. List the exchange rates your company converts amounts with, per date range. Call this to justify a converted figure. A statement is computed in its calculation currency and reported in the currency you asked for, so every amount another tool returns has passed through one of these rates; this is where you read which. Also answers what a company can pay or calculate in, and since when. A range applies from its start month until the next one begins — the last one is open-ended. Rates are set per range, so a date earlier than the earliest range has no rate at all: that is the ConversionError a statement reports, and asking for such a `date` returns no range rather than an error. Companies keep a handful of ranges, so the full list comes back unpaginated. ## Parameters Optional. Keep only the range that applies on this day — an ISO-8601 date like '2026-03-14', or a duration relative to now like '-P1Y'. Omitted, every range is returned. ## Response The company's own currency — the base every rate is expressed against. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. The date ranges of exchange rates, oldest first. Empty when the company configured none, or when the requested period falls before the earliest range. Deep link that opens this date range in the Qobra web app — the currencies settings page, browsed to this range. First month these rates apply, `YYYY-MM` — the month [`update_exchange_rates`](/mcp_documentation/tools/update_exchange_rates) takes as `start_month`. A month before the earliest range has no rate at all. Last month these rates apply, `YYYY-MM` — the month before the next range starts. Null on the current range, which has no end. One rate per currency configured on this range, ordered by currency code. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. How many units of this currency one unit of the company currency buys, over this date range (the company currency's own rate is 1). To convert between any two currencies of the same range, take the ratio: an amount in the source currency times `rate` of the target divided by `rate` of the source — the formula the app itself applies. # list_group_dimensions Source: https://docs.qobra.co/mcp_documentation/tools/list_group_dimensions List your company's group dimensions, such as Country or Team, each with its current group count, ordered alphabetically by dimension name. List the group dimensions of your company, with filters and pagination. A dimension is a *kind* of grouping — 'Country', 'Team', 'Segment' — and each holds one group per value (the 'Country' dimension holds one group per country). Reach for this to discover which dimensions exist before filtering list\_groups by one; each row carries the dimension's current group count. Rows are ordered alphabetically by dimension name (A→Z). ## Parameters Filter matched against the dimension name (case-insensitive substring) or, when it is a valid ObjectId, against the dimension id. When True, return only archived dimensions; otherwise only active (non-archived) dimensions. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response The dimension's stable key — the identifier used to match it when synced from an integration, distinct from its display name. Number of groups that belong to this dimension. # list_groups Source: https://docs.qobra.co/mcp_documentation/tools/list_groups List your company's groups by dimension with filters and pagination: each row carries the group's dimension and its current member count. List the groups of your company, with filters and pagination. Groups are a secondary hierarchy organized by dimensions (a 'Country' dimension holds one group per country, and so on). Each row carries the group's dimension and its current member count; use get\_group for the full member list. Rows are ordered alphabetically by group name (A→Z). ## Parameters Filter matched against the group name (case-insensitive substring) or, when it is a valid ObjectId, against the group id. Keep only groups in the dimension with this id — the id list\_group\_dimensions returns. When True, return only archived groups; otherwise only active (non-archived) groups. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this group in the Qobra web app. The group's stable key — the identifier used to match it when synced from an integration, distinct from its display name. The dimension the group belongs to (the kind of grouping, e.g. 'Country' or 'Team'). Null if the dimension is unknown. Number of users currently in the group (attributions active as of today). Past and future members are excluded. # list_help_center_articles Source: https://docs.qobra.co/mcp_documentation/tools/list_help_center_articles List Qobra Help Center articles as summary rows with id, title, and author-written description, ready for a full-body follow-up read. List Qobra's Help Center articles; returns summary rows + pagination. The Help Center is Qobra's public product documentation (synced from Intercom). Use it to ground answers about how Qobra works, then call `get_help_center_article` with a row's `id` (or name) to read the full body. Each row carries the article `id` (for the follow-up call), the `title`, the author-written `description` (`None` when the article has none — there is no generated summary), the `url` and `updated_at`. Rows are otherwise ordered alphabetically by title. Results are scoped to the caller's in-app access: articles in a collection the caller isn't entitled to are omitted. ## Parameters Keyword filter, matched case-insensitively as a substring of the article title or body. Results are ranked by relevance (most matches first). Omit it to list every article, alphabetically by title. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response The author-written description. Public URL of this Help Center article (an external help.qobra.co link, not a Qobra web-app page); null if unpublished. # list_imports Source: https://docs.qobra.co/mcp_documentation/tools/list_imports List your company's data imports, filtered by resource type, status, dates or errors, each with a deep link to the run in the Qobra web app. List your company's data imports, most recent first. An import is one run of Qobra pulling data in — a CRM/integration refresh, a CSV upload or an API push — into a data table, a quota or the user directory. Each row summarizes one run: its status, what triggered it, which resource types it touched, an overall progress, an error flag and a warning flag. Combine the filters (AND) to zoom in, e.g. resource\_type='quota' + status='finished', or error=True to triage failed runs. Fetch get\_import for the per-batch breakdown (record counters, error detail) of a single run. Only the last 12 months are queryable — older imports are stale and not returned. You only see imports that touched a resource type you may read, and filtering by a resource\_type outside your permissions is refused with a permission error. To find out why one table or one connection fails to sync, pass its `data_table_id` or `integration_id` with `error=True`. One scheduled refresh imports every table of a connection, so the error is judged on the batches those ids select, not on the whole run. Then call [`get_import`](/mcp_documentation/tools/get_import) on a returned run and read the failed batch's `error_category`, `error_type` and `error_description`. `error_category` tells you why the connection failed (for example `authentication` means you need to reconnect it, `rate_limit` means you should retry later). ```json theme={null} { "integration_id": "6650f1c2a8b4e1d2c3f4a5b6", "error": true, "created_after": "-P7D" } ``` A run can also finish without an error and still drop rows: a users push skips a duplicated line, or a sync rejects a value it cannot map. Pass `warning=True` to find those runs, then read the batch's `rejected_rows` in [`get_import`](/mcp_documentation/tools/get_import). Each rejected row comes with its reason and a locator, such as the CSV line it came from or the user it is about. That is where you answer "why didn't my users come in?". ```json theme={null} { "resource_type": "user", "warning": true, "created_after": "-P7D" } ``` ## Parameters Keep only imports that touched this resource type: 'data\_table', 'quota' or 'user'. Omit for every type. Keep only imports in this state: 'scheduled', 'started' or 'finished'. Omit for every state. Keep only imports created at or after this instant. Accepts an absolute ISO-8601 date/time ('2026-07-01') or a duration relative to now ('-P7D' for the last 7 days). Omit for no lower bound. Keep only imports created at or before this instant. Same accepted forms as created\_after. Omit for no upper bound. Keep only imports with a batch for the data table with this id — the id list\_data\_tables returns. Omit for every table. Keep only imports with a batch synced from the integration with this id — the id list\_integrations returns. Omit for every integration. True keeps only imports with at least one failed batch; False keeps only imports where every batch succeeded. Only the batches you can read count, and with data\_table\_id or integration\_id only the ones they select: a run where another table failed is not in error for this one. Omit for both. True keeps only imports with a batch that rejected or skipped rows, even if it did not fail; False keeps only imports with none. Counted like error. Omit for both. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link that opens this import expanded in the Qobra web app, on the history page of a resource type it touched. Null only for an import with no batch yet, which no history page lists. Overall status: 'scheduled', 'started' or 'finished'. One of: `scheduled`, `started`, `finished`. What kicked the import off, e.g. 'scheduled\_refresh', 'integration\_refresh' or 'quota\_sync'. Null when unknown. One of: `scheduled_refresh`, `integration_refresh`, `integrations_with_frequency_refresh`, `braqoffice_refresh`, `users_creation`, `user_integration_sync`, `user_file_import_refresh`, `user_attributes_creation`, `groups_creation`, `data_table_refresh`, `data_table_settings_update`, `field_creation`, `quota_creation`, `quota_sync`, `quota_refresh`, `quota_settings_update`. Overall completion 0–100, averaged across the import's batches (100 when it has no batch). True when a batch you can read ended in error; get\_import says which and why. True when a batch you can read finished but rejected or skipped some rows; get\_import lists them. A run can carry a warning without an error. Distinct resource types the import touched — any of 'data\_table', 'quota', 'user'. The user who triggered the import; null for a system or scheduled import. # list_integrations Source: https://docs.qobra.co/mcp_documentation/tools/list_integrations List the systems your company syncs data from, each with its id, type, category, and paused state, ready to filter data tables by integration. List the systems your company syncs data from. An integration is one connection to an external system — a CRM, an HRIS, a database, a spreadsheet — whose data lands in Qobra as data tables. Call this to learn which connections exist, then pass one's id as the `integration_id` filter of list\_data\_tables to see only its tables. Each row says whether the connection is `in_use` — whether a data table or a non-archived quota is synced from it. To see *what* depends on one, chain its id into list\_dependencies with `resource_type='integration'`. Neither counts the users, attributes and groups an HRIS connection syncs, so read `in_use: false` as "no table and no quota", not as "safe to disconnect". To diagnose a sync that fails, chain its id into [`list_imports`](/mcp_documentation/tools/list_imports) as `integration_id` with `error=True`. To narrow to one table, pass a table's `data_table_id` from list\_data\_tables instead. Then call [`get_import`](/mcp_documentation/tools/get_import) on a returned run for the failed batch's error detail. Rows are ordered by connection name. The public-API connection is listed too, since tables pushed through the API carry its id like any other. Integrations pending deletion are never returned. Takes no arguments. ## Response One of: `kombo`, `salesforce`, `pipedrive`, `microsoft_dynamics`, `hubspot`, `odoo`, `zoho`, `attio`, `postgresql`, `gcp-postgresql`, `snowflake`, `snowflake-keys`, `redshift`, `microsoft-azure-sql`, `microsoft-azure-synapse`, `bigquery`, `s3`, `sftp`, `netsuite`, `public_api`. Deep link that opens this connection in the Qobra web app — the integrations settings page, focused on it. HRIS and API connections still resolve to the plain settings page: they are listed there but have no per-connection dialog to open. What kind of system this connects to — crm, hris, database, spreadsheet, api, and so on. One of: `file_system`, `database`, `crm`, `hris`, `api`. True when the integration is paused: it keeps its tables and their records, but stops refreshing them. True when at least one data table or non-archived quota is synced from this connection — the inverse of the UNUSED badge the web app shows on its card. It counts those two kinds only: an HRIS connection syncing users, their attributes and their groups reads as false while still feeding all three, which disconnecting would unsync. So false means no table and no quota depend on it, never that disconnecting it is safe. Call list\_dependencies with `resource_type='integration'` for the resources behind it. # list_plans Source: https://docs.qobra.co/mcp_documentation/tools/list_plans List the compensation plans of your company with filters and pagination: each row carries the plan's section and its compensation count. List compensation plans of your company, with filters and pagination. Each plan carries its section and its compensation count; open a plan with get\_plan for each compensation's periodicity, date range and the reporting metrics it feeds, plus the plan's members. The rest of the calculation behind a compensation is not exposed there — read a statement's computed breakdown with get\_statement\_compensation. Rows are ordered alphabetically by plan name (A→Z). ## Parameters Filter matched against the plan name (case-insensitive substring) or, when it is a valid ObjectId, against the plan id. When True, return only archived plans; otherwise only live (non-archived) plans. Deleted plans are never returned. Id of the sandbox to read instead of the live environment — the id list\_sandboxes returns. Omit it to read the live environment, the company's real compensation data, which is the right choice unless the question is explicitly about a sandbox. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this plan in the Qobra web app. Folder/group this plan is filed under (a concept shared with reports); null if unfiled. How many compensations the plan holds. Open the plan with get\_plan for each compensation's periodicity, date range and the reporting metrics it feeds, plus the plan's members. The rest of the calculation behind a compensation is not exposed there — read a statement's computed breakdown with get\_statement\_compensation. # list_quota_values Source: https://docs.qobra.co/mcp_documentation/tools/list_quota_values List one quota's per-user, per-period target values with pagination, including each value's owner, period, and currency for amount quotas. List one quota's target values across users and periods, with pagination. Resolve a single quota, then return its per-user, per-period target values. The quota's `type` and `frequency` are surfaced once in the `quota` header; each row carries `value`, the owning `user` (an `{id, email, name}` reference), the `period` (big-endian, at the quota's frequency), and `currency` only when the quota is amount-typed. Pair with `list_quotas` to find the quota first. Filters combine with AND. You only see quotas and values you have access to; a quota you can't access is reported as not found. Pass `view_id` to narrow to a single view's rows. Rows are ordered by period ascending (earliest first), then by user. Each row exposes a `resource_url` pointing at its parent quota. The result also carries a list-level `application_url` that reopens the exact list you just fetched — the quota's page with the chosen view selected. It is only set when a view was passed; otherwise it is null and the quota's own `resource_url` already opens the page. ## Parameters Id of the quota to read values from — the id list\_quotas returns. Single period, in the quota's own frequency format: YYYY-MM (monthly), YYYY-QX (quarterly), YYYY-SX (semesterly) or YYYY (annually). A period in the wrong format for this quota's frequency is rejected with the expected format. Omitted: values for every period. User filter — a name/email substring or an ObjectId, matched the same way as list\_users search. Filters the values to the matching users. Omitted: every user you can see. Id of the quota view to restrict the rows to its users, from the `views` of the quota's row in list\_quotas. Omitted: every user you can see. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this quota in the Qobra web app. Unit of the target value: amount (monetary, has a currency), percentage, or float (plain number). One of: `amount`, `percentage`, `float`. The quota's default currency for amount targets: 'company' (the company currency), 'user' (each user's payment currency) or 'calculation' (the user's calculation currency). A target value that carries its own currency overrides this default. Only meaningful for amount quotas. One of: `company`, `user`, `calculation`. The quota views the caller may query for this quota's values — pass one's id as `view_id` to list\_quota\_values to narrow the rows. Empty when no view is relevant. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. Deep link to the quota this value belongs to (a quota value has no page of its own, so it points at its parent quota). Deep link that reproduces this list in the web app — the quota's page with the chosen view selected. Null when no view filter was applied (the quota's own resource\_url already opens the page). # list_quotas Source: https://docs.qobra.co/mcp_documentation/tools/list_quotas List your company's quotas with their measurement type, frequency, archived flag, and the saved views usable to narrow their quota values. List your company's quotas, with filters and pagination. Each quota carries its measurement type (amount / percentage / float), its frequency (monthly / quarterly / semesterly / annually) and whether it is archived. You only see the quotas you have access to. Each quota also lists the `views` whose id you can pass as `view_id` to `list_quota_values` to narrow the rows. Rows are ordered alphabetically by quota name (A→Z). ## Parameters Filter matched against the quota name (case- and accent-insensitive substring) or, when it is a valid ObjectId, against the quota id. When True, return only archived quotas; otherwise only active (non-archived) quotas. Id of the sandbox to read instead of the live environment — the id list\_sandboxes returns. Omit it to read the live environment, the company's real compensation data, which is the right choice unless the question is explicitly about a sandbox. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Pass the next\_offset returned by the previous call, or None for the first page. ## Response Deep link to open this quota in the Qobra web app. Unit of the target value: amount (monetary, has a currency), percentage, or float (plain number). One of: `amount`, `percentage`, `float`. The quota's default currency for amount targets: 'company' (the company currency), 'user' (each user's payment currency) or 'calculation' (the user's calculation currency). A target value that carries its own currency overrides this default. Only meaningful for amount quotas. One of: `company`, `user`, `calculation`. The quota views the caller may query for this quota's values — pass one's id as `view_id` to list\_quota\_values to narrow the rows. Empty when no view is relevant. Each view also carries an `access` roster — roles and users who can read it — populated when you may share this resource, `null` otherwise. Who can read this view, split into roles and users, each with the level they hold. Only the tools that list views for you to choose between resolve it — list\_quotas, list\_data\_tables and get\_identity — and only for callers who may share the resource. Null everywhere else, so null is silence about the roster (either not asked for, or not yours to see), never an empty one: a caller who may see it and finds nobody gets empty `roles` and `users` instead. The level this role holds on the whole resource category, which reaches every view of it without a share — `read` (view it), `write` (edit and re-share it) or `delete` (also remove it), each level including the ones before it. Null when the role only got this view through a share. One of: `read`, `write`, `delete`. The level granted to this role on this view specifically — `read` (view it), `write` (edit and re-share it) or `delete` (also remove it), each level including the ones before it. Null when the role reaches the view through its category access rather than a share. One of: `read`, `write`, `delete`. The level this view is shared with the user at, individually, on top of whatever their role already grants — `read` (view it), `write` (edit and re-share it) or `delete` (also remove it), each level including the ones before it. One of: `read`, `write`, `delete`. # list_reports Source: https://docs.qobra.co/mcp_documentation/tools/list_reports List your company's reports with filters and pagination: each row carries a report's name, type, tracked data table, section, and access. List your company's reports, with filters and pagination. Each row summarizes one report: its name, type, description, the tracked data table for record reports, and the section it is filed under. Callers with write access on reports also get who can view each report (`access` — the roles, including roles with default access such as admins, and the users it is shared with); read-only callers get `access: null`. A report's internals — fields, filters, pivot-table and chart configuration — are intentionally not returned. You only see the reports you have access to; the rest are omitted. Rows are ordered alphabetically by report name (A→Z). ## Parameters Filter matched against the report name (case- and accent-insensitive substring) or, when it is a valid ObjectId, against the report id. Filter restricting results to statement or record reports. Omit to return both kinds. One of: `statement`, `record`. Id of the sandbox to read instead of the live environment — the id list\_sandboxes returns. Omit it to read the live environment, the company's real compensation data, which is the right choice unless the question is explicitly about a sandbox. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this report in the Qobra web app. What the report is built on: statement (payout statements) or record (the rows of the tracked\_datatable). One of: `statement`, `record`. Source data table tracked by a record report, with its id and name; null for statement reports. The dashboard tiles this report can render: `chart` when a chart is configured on it, `pivot_table` when a pivot table is. Empty when neither is — the report then has no tile to show on a dashboard. The section the report is filed under (id and name), or null when unfiled. Who can view the report, split into roles and users. Each entry carries its id, name, and access level. Only returned to callers with write access on reports; null for read-only callers. One of: `read`, `write`, `delete`. One of: `read`, `write`, `delete`. One of: `read`, `write`, `delete`. # list_requests Source: https://docs.qobra.co/mcp_documentation/tools/list_requests List the requests you can access with simple filters and pagination, ordered by most recent activity, each with a direct resource URL. List the requests you can see, with simple filters and pagination. A request can gather members, an assignee and a topic, and link statements. You only see the requests you have access to — those you're a member of, plus any shared with you. Rows are ordered by most recent activity first (last modification date). Each row exposes a `resource_url` that opens that single request. The result also carries a list-level `application_url` that reopens the requests board on the same view and status tab you just queried. Because the board is always scoped to one status tab, this link needs a `status`; it is null without one, and null when the served set matches no single board view. ## Parameters Either a built-in token — "assigned\_to\_me" or "my\_requests" (the ones you created) — or the id of a request view shared with you, never its name. Omitted: every request you can access. Free-text filter matched against the request name, its full id, or its reference (the # reference shown in the UI is the last 8 characters of the id; a leading # is accepted). Status filter. One of open or closed. One of: `open`, `closed`. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this request in the Qobra web app. The #-style reference shown in the UI. Admin-configured category used to classify the request; null if none. open (awaiting handling) or closed (resolved). One of: `open`, `closed`. The linked deal/record when the statement is record-based; null otherwise. Deep link that reproduces this list in the web app — the requests board opened on the served view and status tab. Null when `status` was omitted (the board shows one status tab at a time, so a link cannot faithfully reproduce a multi-status result), and null when the served set matches no single board view. # list_roles Source: https://docs.qobra.co/mcp_documentation/tools/list_roles List your company's permission roles alphabetically with a name/id filter and pagination; each row carries the role's headcount, default and admin flags. List your company's permission roles and how many users hold each. A role is what decides what a user may see and do; use `get_role` for the permissions behind one. Reach for this to answer questions about the shape of the organization's access — how many people are admins, which roles exist, which one new users get by default. Rows are ordered alphabetically by role name (A→Z). Qobra support's own god role is never listed, and the headcount counts non-archived users only. This tool needs the company-settings *roles* permission (READ on the Role company setting). The web app does not require it for a caller merely reading role names, but an organization's permission structure is administrative data, and gating it like `get_role` means a caller who can list a role can always read it. ## Parameters Filter matched against the role name (case-insensitive substring) or, when it is a valid ObjectId, against the role id. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this role in the Qobra web app. Whether this is the role newly-created users get unless another one is picked. Exactly one role of a company is the default. Whether this is the company's built-in Admin role, which holds every permission and cannot be edited. Number of non-archived users currently holding this role. # list_sales_coach_discussions Source: https://docs.qobra.co/mcp_documentation/tools/list_sales_coach_discussions List Sales Coach discussions, newest first, to review what users asked the Qobra AI assistant and which topics the agent had to escalate. List the company's Sales Coach discussions, most recently opened first. A discussion is one conversation a user had with the Sales Coach — the Qobra AI assistant they ask about their own statement — their questions and the agent's answers. Reach for it to review what people actually asked the AI: recurring confusion about a plan, topics the agent had to escalate, a specific rep's questions. Qobra's other AI agents have no equivalent tool yet, so this is not the full picture of what people ask the product's AI. Reserved to full statement access, so it returns every discussion in the company. Filters combine with AND. Each row is a summary — the opening question, how many questions followed, whether the agent escalated; fetch get\_sales\_coach\_discussion for the full transcript of one. Keep paginating for as long as next\_offset comes back set: a page can be shorter than limit — empty, even — without being the end of the list. ## Parameters Keep only the discussions this person had with the agent — the id list\_users returns, or their exact email. Omit for every author. Keep only discussions about the statement with this id — the id list\_statements returns. Combined with period, it narrows to the intersection, which is empty unless that statement belongs to that period. Keep only discussions about statements of this period, in YYYY-MM format (e.g. 2026-03). Omit for every period. Keep only discussions opened at or after this instant. Accepts an absolute ISO-8601 date/time ('2026-07-01') or a duration relative to now ('-P7D' for the last 7 days). Omit for no lower bound. Keep only discussions opened at or before this instant. Same accepted forms as start. Omit for no upper bound. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Pass the next\_offset returned by the previous call, or None for the first page. ## Response Deep link to the statement the discussion is about, in the Qobra web app. Discussions have no page of their own. The user who asked the questions. The statement a discussion is about — the subject every question is asked against. Statement period, YYYY-MM. The user the statement belongs to. Usually also the discussion's author (a rep asking about their own statement), but an admin or manager can question someone else's statement. The opening question, verbatim — what the user came for. Null for a discussion with no user message yet. Number of questions the user asked. Excludes the replies they gave to the agent's own clarifying questions, which are stored as user messages too. Total messages in the transcript, questions and answers together. Number of times the agent escalated to a human, opening a request. Above zero means the agent could not answer on its own — fetch get\_sales\_coach\_discussion for the escalation detail. When the discussion was opened — the sort key, most recent first, and what the start/end filters match. Last activity on the discussion. # list_sandboxes Source: https://docs.qobra.co/mcp_documentation/tools/list_sandboxes List your company's sandboxes, copies of its real compensation data, and get the ids to pass as the sandbox_id argument of other tools. List your company's sandboxes — copies of its real compensation data. A sandbox lets you design and test compensation without touching the real data: it has its own plans, quotas, statements and reports. Call this to learn which sandboxes exist, then pass one's id as the `sandbox_id` argument of another tool to read that sandbox. Every tool reads the company's real data unless you pass such an id, so you only need this when the question is explicitly about a sandbox. Rows carry no deep link: the web app has no sandbox page — it switches from its own selector. Takes no arguments; deleted sandboxes are never returned. ## Response Pass this id as the `sandbox_id` argument of another tool to read that tool's resources inside this sandbox. Whether the sandbox holds usable data. A freshly created one is still being duplicated and reads as not ready; passing its id to another tool is refused until it flips. Whether reports and dashboards can be read in this sandbox. False for a sandbox created without reporting, or whose reporting duplication is not published yet — the report tools refuse such a sandbox while the other tools still serve it. First period the sandbox computes statements for, as `YYYY-MM`. Null when the sandbox carries no period window. Last period the sandbox computes statements for, as `YYYY-MM`. Outside this window the sandbox holds no statement. # list_statement_periods Source: https://docs.qobra.co/mcp_documentation/tools/list_statement_periods List statement totals broken down by period, currency, and reporting stage, with summed amounts and statement counts per row, newest first. List how your statements break down, one row per period, currency and stage. Returns one row per period (`YYYY-MM`, newest first), currency and reporting `stage` (ERROR / OPEN / VALIDATED / LOCKED / CLOSED), each with the summed `total_amount` in that `currency` and how many statements it covers. Use it to answer "how are we managing our statements?" — e.g. whether every past period is already CLOSED. Amounts are never summed across currencies, so a period spans several rows when its statements do. Only the statements you have access to are counted — the same set `list_statements` returns. Amounts are converted into the currency named by `currency_mode` (each statement's payment currency by default). To compare totals across periods, pass `currency_mode='company'` so every period reports in the single company currency you can line up directly — the default (`payment`) can split one period across several currency rows, which is not comparable period to period. ## Parameters When True (default), only periods from the last 12 months (relative to today) are returned. When False, the full history is returned. Which currency each total\_amount is converted into and labelled with: 'payment' (default) — the currency the user is paid in; 'calculation' — the currency the plan computes in; 'company' — the company's global currency. The amount and its currency always agree. Prefer 'company' when comparing or aggregating totals across users or periods. One of: `company`, `payment`, `calculation`. Id of the sandbox to read instead of the live environment — the id list\_sandboxes returns. Omit it to read the live environment, the company's real compensation data, which is the right choice unless the question is explicitly about a sandbox. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. ## Response One row per (period, currency, stage) combination. Ordered by period (newest first), then currency, then stage. The period in `YYYY-MM` format. Deep link that opens the statements board on this period in the Qobra web app. The ISO currency code that `total_amount` is expressed in. Amounts are never summed across currencies, so a period appears once per currency present in its statements. The statement reporting stage: * ERROR: the statement's calculation errored out and needs attention. * OPEN: created, not yet in the validation workflow. * VALIDATED: in the validation workflow, awaiting freeze. * LOCKED: frozen, not yet paid. * CLOSED: sent to payroll. One of: `ERROR`, `OPEN`, `VALIDATED`, `LOCKED`, `CLOSED`. Sum of `total_amount` over the statements in this period, currency and stage — never summed across currencies. How many statements fall in this period, currency and stage. # list_statement_records Source: https://docs.qobra.co/mcp_documentation/tools/list_statement_records List the frozen records of one compensation scope on a statement: the snapshot rows that fed the commission, with displayable cell values. List the frozen records of one compensation scope on a statement. A compensation calculates over one or more scopes (a data table filtered to a perimeter); each scope's records are frozen into the statement at calculation time. This returns those snapshot records — the deals/rows that fed the commission — each with its displayable cell values (amounts in the statement's calculation currency, picklists as labels, user references as `{id, email, name}` objects, linked records by name). This is the frozen snapshot, not the live data table (use list\_data\_table\_records for the current data). **A record's `values` are positional against the result's `fields`** — the Nth value is the Nth column, so read that header to know a cell's name and type. The column's name and type are stated once there, and the shared `calculation_currency` once at the result level, rather than repeated on every cell. A column a record has no value for holds a null, so every row is the same width as the header. Identify the statement by `statement_id` (from `list_statements` / `get_statement`) and the scope by `scope_id` (from `get_statement_compensation`'s scopes). A scope belongs to exactly one compensation, so its id is enough — there is no compensation argument. A statement or scope you can't access reads exactly like one that doesn't exist, so the error never reveals anything outside your scope. Records keep the statement's frozen scope order — the index they were snapshotted in at calculation time. ## Parameters Id of the statement the scope was frozen into — the id list\_statements or get\_statement returns. Id of the scope to read, from the `id` of one of get\_statement\_compensation's scopes. Free-text filter, a case-insensitive substring matched against each record's text and linked-record name fields. Omitted returns the scope's records unfiltered. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response The scope's displayable columns (name + type), listed once for the whole page — each record's `values` align positionally against this list (the Nth value belongs to the Nth field). One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`, `object`, `group`, `record_status`. The currency every AMOUNT value in the records is in. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. The record's display name (its name-field value), falling back to its source id then its record\_id when no name field exists. The record's cell values, one per column, in the same order as the result's `fields` — read the Nth value against the Nth field to know its name and type. Amounts are in the result's `calculation_currency`, stated once there rather than on every cell. A column this record has no value for is null. # list_statements Source: https://docs.qobra.co/mcp_documentation/tools/list_statements List the statements you can access with simple filters, pagination, and a currency mode to aggregate comparable totals in one currency. List the statements you can see, with simple filters and pagination. You only see the statements you have access to. To aggregate or compare a single comparable total across users or periods, pass `currency_mode='company'` so every amount lands in one currency, and report that single total — never sum amounts in different currencies. The default (`payment`) leaves each amount in the user's own pay currency, which is not comparable across a multi-currency team. Each row's `conversion` block reports the currency its amounts were stored in and the rate applied, so a converted figure can be justified (`list_exchange_rates` holds the company's rates per period). A statement whose company has no rate for the requested currency is refused rather than served in another one. Each row exposes a `resource_url` that opens that single statement. The result also carries a list-level `application_url` that reopens the statements board on the queried period. It is always set because `period` is required. ## Parameters Period in YYYY-MM format (e.g. 2026-03). Required. Free-text filter matched against the statement's user (name or email), same matching as list\_users search. Status filter. One of ongoing (open / not yet locked), locked (frozen but not paid), paid (sent to payroll), or error (the calculation errored out). The error condition is bundled into this single field. If True, only statements with at least one manual amount adjustment (overwritten). If True, only statements that have at least one linked request you can access. Keep only statements computed from the compensation plan with this id — the id list\_plans returns. Id of the statement view to narrow the rows to its users, from get\_identity (the views of your statement access). A filtering aid only: it never widens what you can see. Id of the manager whose team to narrow to — everyone below them in the hierarchy (all levels, not just direct reports). The id list\_users returns, or the manager's exact email. It only filters within what you can already see — a teammate outside your access stays hidden. Which currency each total\_amount is converted into and labelled with: 'payment' (default) — the currency the user is paid in; 'calculation' — the currency the plan computes in; 'company' — the company's global currency. The amount and its currency always agree. One of: `company`, `payment`, `calculation`. Id of the sandbox to read instead of the live environment — the id list\_sandboxes returns. Omit it to read the live environment, the company's real compensation data, which is the right choice unless the question is explicitly about a sandbox. Only a sandbox id is accepted here — never a sandbox name, and never the live environment's own id. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Pass the next\_offset returned by the previous call, or None for the first page. ## Response Deep link to open this statement in the Qobra web app. Overall processing state: error (calculation failed), ongoing (open, not yet locked), locked (frozen, not yet paid), paid (sent to payroll). A paid statement is always frozen too. Distinct from the per-period reporting stage of list\_statement\_periods. One of: `error`, `ongoing`, `locked`, `paid`. Net amount to be paid out on this statement, expressed in `currency` — already reflects draw and manual payments, so it is not just the sum of the compensation lines. The ISO currency code that total\_amount is expressed in. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. When the statement last finished computing and was up to date; null if it never reached that state. How every amount on this statement reached `currency` — the currency it was stored in and the rate applied. The currency the statement stores its amounts in — its calculation currency, the one its plan computes in. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. The exchange rate applied to every amount here: an amount in `source_currency` times this rate gives the amount in `currency`. 1 when both are the same currency, i.e. nothing was converted. Read list\_exchange\_rates for the company's rates and the periods they apply to. Deep link that reproduces this list in the web app — the statements board opened on the queried period. Always present, since `period` is required. # list_user_attributes Source: https://docs.qobra.co/mcp_documentation/tools/list_user_attributes List the schema of user attributes your company defines: name, type, picklist options, readonly flag and source connection, in display order. List the *schema* of user attributes your company defines — what it records about its people (Segment, Hiring date, Region…), of what type, and which labels a picklist accepts. This is the schema behind the `attributes` array [`get_user`](/mcp_documentation/tools/get_user) returns: reach for `list_user_attributes` when you need each attribute's type, whether it is synced from a source system, or the exact labels a picklist accepts; reach for `get_user` when you need one specific user's values. Rows come in the company's own display order (the order shown in the Qobra web app), so a page is directly usable as a form or a rendering layout. Access is gated by **READ** permission on **Users**. ## Parameters Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Id of the attribute — pair it with the `id` each `get_user` attribute entry carries to map a user's value back to its definition, even when two attributes share a display name. What the company says this attribute means, in their own words. Null when they wrote none. Attribute data type: amount, percentage, float, string, bool, date, picklist, multipicklist, or user (a reference to another Qobra user). One of: `float`, `amount`, `percentage`, `string`, `picklist`, `multipicklist`, `bool`, `date`, `user`. Selectable option labels for a PICKLIST / MULTIPICKLIST attribute, in definition order; null for every other type. Archived options are left out — they can no longer be picked, though a user set to one before it was archived still carries it, so a value `get_user` returns need not appear in this list. True when the value cannot be edited in Qobra — it is owned by the source system that syncs it. The company connection this attribute is synced from; null when it is maintained in Qobra itself. An attribute whose source connection has since been removed reads as maintained in Qobra rather than naming a link that no longer syncs. # list_users Source: https://docs.qobra.co/mcp_documentation/tools/list_users List the users of your company alphabetically with filters and pagination; rows carry integration links and last HRIS sync for coverage checks. List users of your company, with simple filters and pagination. Rows are ordered alphabetically by name (A→Z). Each row already carries the user's current direct manager, integration links and last HRIS sync. To group users by manager, or to answer a sync-coverage question ("who is missing from the HiBob sync?"), paginate this tool and read `manager` or `integrations`. Never call get\_user once per user. Reach for get\_user only when you need one user's manager history, groups, plans or custom attributes. ## Parameters Free-text filter matched against the user's normalized search field (name + email, lowercased, unidecoded). Status filter. One of inactive, pending, active, archived. Defaults to non-archived users. One of: `inactive`, `pending`, `active`, `archived`. Id of the manager whose team to narrow to — everyone below them in the hierarchy (all levels, not just direct reports). The id list\_users returns, or the manager's exact email. With manager\_id, keep only the people reporting directly to that manager, not the levels below them. Requires `manager_id`; passing it alone returns an input error. Defaults to `false`. Page size, between 1 and 250. Defaults to 50. Row offset into the result set — the number of rows to skip before this page. Any offset >= 0 is accepted; it does not have to be a multiple of limit, so you can start from an arbitrary row. Pass the next\_offset returned by the previous call to walk the pages, or None for the first page. ## Response Deep link to open this user in the Qobra web app. User lifecycle state: pending (invited, not logged in yet), active, inactive (deactivated), archived (has left the company; hidden by default in list\_users). One of: `inactive`, `pending`, `active`, `archived`. The user's permission role — what decides what they may see and do, not their status. Pass its id to get\_role to read the permissions behind it. The user's direct manager today; null when they report to no one. A user has one manager at a time, so grouping users by manager takes one paginated list\_users, never a call per manager. get\_user lists their past managers with dates. Currency the user is actually paid in. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. Currency the user's commissions are computed in. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. Whether the user holds a paid Qobra license (a seat). Only licensed users have their compensations calculated and a statement to view — an unlicensed user's statement is never computed, which is the usual reason someone 'can't see X's statement'. A newly-arrived user typically needs a license added; a departed user who no longer appears in the data is a candidate for archiving to free their seat. True when the user is NOT licensed yet has compensations that would otherwise be calculated — the statement can't be computed for lack of a seat. This is what raises the red warning banner at the top of a statement; resolve it by assigning the user a license. The user's links to the company's connected integrations (HRIS, CRM…), one entry per integration they are matched in. An empty list means the user is matched in none — which is what 'this user is missing from the \ sync' looks like. Use list\_users to answer sync-coverage questions over the whole company in one call; never call get\_user per user. The company connection this link belongs to. The user's id in the customer's own system. When this user's identity was last refreshed from the HRIS integration. Null means never synced — either the company has no HRIS connected, or this user has never been matched in it. # split_record Source: https://docs.qobra.co/mcp_documentation/tools/split_record Split a data table record between several users — a deal shared by two reps. Split a record between several users — a deal shared by two reps. Each part becomes a new record named after the original ("\ - Split \#1", …): a copy of it, with its own user in `user_field_id` and its own values for the numeric fields listed. The original is archived. Nothing checks that the parts add up to the original, as in the web app, so send values that do. Call get\_data\_table first for the id of the user field and of the numeric fields to split, and for its `adjustment_reasons`. Statements are not recalculated by this call, just as when a record is split in the web app. They pick the parts up at the next automatic recalculation, or when someone refreshes them. Requires write permission on data tables. Not available on demonstration companies. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Id of the data table holding the record — the id list\_data\_table\_records was called with. Id of the record to split, in that table — the id list\_data\_table\_records returns. Id of the user field that says whose each part is — the deal owner, typically. A field of type user from get\_data\_table. The parts, at least two: one new record per part. Id or email of the user this part goes to, as a user reference carries them. The numeric fields this part overrides. A field left out keeps the original record's value. Id of a numeric field (amount, float or percentage) of the record — a field id get\_data\_table returns. This part's value for the field: a number for an amount or a float, and the ratio for a percentage (0.8 for 80%). Why the record is split — one of the table's `adjustment_reasons` from get\_data\_table. Required only when that list is not null; omit it otherwise. Optional note kept in the records' history. The call is refused, with a message naming the problem, when no data table has the `data_table_id`, when no record of that table has the `record_id`, when the record is already archived, when fewer than two parts are passed, when `user_field_id` is not a user field of the table, when a part lists a field that is not numeric or lists a field twice, or when the table requires a `reason` and none from its `adjustment_reasons` was passed. ## Example ```json theme={null} { "data_table_id": "665f1c2e8a4b3d0012a7e9f1", "record_id": "665f1c2e8a4b3d0012a7ea10", "user_field_id": "665f1c2e8a4b3d0012a7e9f6", "splits": [ { "user_id": "665f1c2e8a4b3d0012a7eb01", "values": [{ "field_id": "665f1c2e8a4b3d0012a7e9f3", "value": 9000 }] }, { "user_id": "665f1c2e8a4b3d0012a7eb02", "values": [{ "field_id": "665f1c2e8a4b3d0012a7e9f3", "value": 6000 }] } ], "reason": "Shared deal", "comment": "Co-sold by two account executives." } ``` ## Response The table of the records, whose `fields` header names their values in order — the same row list\_data\_tables returns. See [`list_data_tables`](/mcp_documentation/tools/list_data_tables) for the full field breakdown. The records created, one per part, in the order sent — the same rows list\_data\_table\_records returns. See [`list_data_table_records`](/mcp_documentation/tools/list_data_table_records) for the full field breakdown. The record that was split, now archived. # update_dashboard Source: https://docs.qobra.co/mcp_documentation/tools/update_dashboard Rename a dashboard, add reports to it and arrange its tiles, in one call. Rename a dashboard, add reports to it, arrange its tiles — any of the three, or all of them in one call. Pass `name` and/or `description` to change how the dashboard reads. Pass `reports` to add reports to it as the tiles each one can render — a report carrying both a chart and a pivot table becomes two tiles, one carrying only a chart becomes one, and one carrying neither is skipped, there being nothing to show. A report's `visualization_types` narrows that to its chart or its pivot table when you want only one of them; `list_reports` names what each report can render in its `available_visualizations`. Removing a tile is still a job for the Qobra web app. Pass `layout_rows` to say how the tiles sit on the page. It describes the **whole** arrangement, top to bottom, and it is applied after the reports are added — so one call can add reports and place them, naming each by its `report_id` since a new tile has no `visualization_id` yet. A tile you leave out is not removed: it keeps its place, appended on a row of its own. An empty list resets the page, every tile on a row of its own. Pass at least one of `name`, `description`, `reports` or `layout_rows` — a call with none of them has nothing to update. **Read the `outcome` on every `tile_changes` row before reporting success.** Adding never fails over one bad report: it adds what it can and says so per row. A report can come back `not_found`, `no_visualization` when it has no chart or pivot table yet, `type_not_available` when it has none of the types you asked for, or `already_added`. **Read the `outcome` on every `layout_changes` row too.** Arranging never fails over one bad entry either: it places what it can and says so per entry. `layout` is the page as it now stands, read back from the dashboard: where every tile sits, the ones this call added included. Read it rather than assuming your rows were taken verbatim: a row holds at most three charts or two pivot tables, and one that overflows is split. You need write access on the dashboard itself. A dashboard shared with you read-only is refused with a message saying the share is read-only; pick one you can edit from [`list_dashboards`](/mcp_documentation/tools/list_dashboards), or create a new one with [`create_dashboard`](/mcp_documentation/tools/create_dashboard). A dashboard you cannot read at all is refused as not found. Available only where the Qobra write tools beta is enabled. Every write to the live environment is recorded in the audit trail as the `Qobra MCP` agent; writes to a sandbox are not audited. ## Parameters Id of the dashboard to update — the id list\_dashboards returns, or the one create\_dashboard just returned. The dashboard names the environment this writes to, so there is no sandbox argument. New display name for the dashboard. Omit to leave the current name as it is. New free-text note about what the dashboard is for. Omit to leave the current description as it is. Reports to add, each as every tile it can render — its chart, its pivot table, or both, unless its visualization\_types narrows it. Omit to leave the dashboard's tiles untouched. Id of the report to add, as list\_reports returns it. Which of its tiles to add: `chart`, `pivot_table`, or both. Omit to add every tile the report offers, which is what you want unless the caller asked for one of the two; list\_reports names what each report offers in its available\_visualizations. The dashboard's rows, top to bottom; each row lists one or more tiles left to right. An entry is a visualization\_id from get\_dashboard, or a report\_id — the report form is how you arrange tiles this same call is adding, since they have no visualization\_id until they exist, and it places every tile of that report. A row holds at most three charts or two pivot tables and is split when it overflows; a tile you leave out keeps its place, appended on a row of its own. Omit to leave the arrangement alone; pass an empty list to reset it, every tile on a row of its own. ## Example Add a report's pivot table to an existing dashboard and put it on the first row, next to a tile already on the page: ```json theme={null} { "dashboard_id": "665f1c2e8a4b3d0012a7ec10", "reports": [ { "report_id": "665f1c2e8a4b3d0012a7eb03", "visualization_types": ["pivot_table"] } ], "layout_rows": [ ["665f1c2e8a4b3d0012a7ed20", "665f1c2e8a4b3d0012a7eb03"] ] } ``` `665f1c2e8a4b3d0012a7ed20` is the `visualization_id` of a tile already on the dashboard, as [`get_dashboard`](/mcp_documentation/tools/get_dashboard) lists it. Every tile left out of `layout_rows` keeps its place on a row of its own. ## Response The dashboard as it now stands after the update. Deep link to open this dashboard in the Qobra web app. What became of each report you passed, one row per tile: a report added as both a chart and a pivot table gets a row for each, and one that adds nothing gets a single row, with visualization\_type null. Read `outcome` on each before reporting success, since a call can succeed having added nothing you asked for. Empty when no reports were passed. What became of this report. "added" for a tile this call put on the dashboard; "already\_added" for one the dashboard already carried, which is how a re-sent call reads as the no-op it was; "not\_found" when no report of yours has that id; "no\_visualization" when the report has neither a chart nor a pivot table to show; "type\_not\_available" when it has tiles but none of the visualization\_types you asked for. One of: `added`, `already_added`, `not_found`, `no_visualization`, `type_not_available`. The report id you passed, echoed back as you spelled it, so the row matches your request even when report is null. The report it resolved to, named — null when it is not one you can read, since naming it would say a report you cannot see exists. The tile this row is about: chart or pivot\_table. Null when no single tile can be named — nothing was added at all. One of: `chart`, `pivot_table`. Why, as a sentence saying what to do next. Null for a tile that landed, there being nothing to act on. The dashboard's rows as they now stand, top to bottom, each tile left to right — read back from the dashboard, so it shows where every tile actually sits. A tile this call added lands on a row of its own at the end. Empty when the dashboard has no tile yet. Rows passed in layout\_rows are applied after the reports are added and come back as the store took them: a row holds at most three charts or two pivot tables and is split when it overflows, and a tile you left out keeps its place on a row of its own. The tiles on this row, left to right. Rows read top to bottom. Id of the tile, as get\_dashboard lists it. The report this tile renders. What the tile renders the report as: chart or pivot\_table. One of: `chart`, `pivot_table`. One row per layout entry you passed, saying what became of it. Empty when no layout\_rows were passed. What became of this layout entry. "placed" for a tile this call positioned; "already\_placed" when an earlier entry of the same layout already placed it, a tile sitting on one row only; "not\_found" when the entry names neither a tile of this dashboard nor a report on it. One of: `placed`, `already_placed`, `not_found`. The layout entry you passed, echoed back. Why, as a sentence saying what to do next. Null for an entry that placed its tile. # update_exchange_rates Source: https://docs.qobra.co/mcp_documentation/tools/update_exchange_rates Set the exchange rates of one date range, creating the range if new. Send the range's whole rate table. Set the exchange rates of one date range, creating the range if new. `start_month` picks the range. The month an existing range starts in updates that range. Any later month creates a new range that applies from the first day of that month until the next range. A new range must start after the latest one. `rates` is the range's **whole** table, not a change to it: every currency the company uses must be listed, or the call is refused. Read the current rates with [`list_exchange_rates`](/mcp_documentation/tools/list_exchange_rates) first and send back the ones to keep. The company currency can be left out: its rate is always 1. A currency the company does not use yet is added to every range at the rate given, because a currency is configured company-wide. To set a different rate on an older range, call the tool again on that range. The call is refused when: * a validated statement was computed with the range's rates, which locks them (a currency new to the company can still be added). Unvalidate those statements in the Qobra web app first. * a new range would start over months a validated statement covers. * `start_month` matches no existing range and is not after the latest one. * a currency is listed twice, a rate is not greater than 0, or the company currency is given a rate other than 1. Sending rates a range already has writes nothing and returns `unchanged`, so the same call can be sent twice. Statements are not recalculated by this call. They convert with the new rates at their next recalculation. Requires write permission on company settings > currency, and multi-currency access for the company. Refused on demonstration companies. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ```json Example arguments theme={null} { "start_month": "2026-10", "rates": [ { "currency_code": "USD", "rate": 1.08 }, { "currency_code": "GBP", "rate": 0.84 } ] } ``` ## Parameters The month the range starts, `YYYY-MM`. The month an existing range starts in (the month of its `start_date` in list\_exchange\_rates) updates that range; any later month creates a new range from its first day. The range's whole table: one rate per currency the company uses, plus any currency to add. The company currency can be left out — its rate is always 1\. The currency, as a code: USD, GBP… How many units of this currency one unit of the company currency buys — the same direction list\_exchange\_rates reports. ## Response The company's own currency — the base every rate is expressed against. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. All date ranges of exchange rates after the write, oldest first. Deep link that opens this date range in the Qobra web app — the currencies settings page, browsed to this range. First month these rates apply, `YYYY-MM` — the month [`update_exchange_rates`](/mcp_documentation/tools/update_exchange_rates) takes as `start_month`. A month before the earliest range has no rate at all. Last month these rates apply, `YYYY-MM` — the month before the next range starts. Null on the current range, which has no end. One rate per currency configured on this range, ordered by currency code. One of: `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BOV`, `BRL`, `BSD`, `BTN`, `BWP`, `BYR`, `BZD`, `CAD`, `CDF`, `CHF`, `CLF`, `CLP`, `CNY`, `COP`, `COU`, `CRC`, `CUC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LTL`, `LVL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRO`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RMB`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLL`, `SOS`, `SRD`, `STD`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYI`, `UYU`, `UZS`, `VEF`, `VND`, `VUV`, `WON`, `WST`, `XAF`, `XAG`, `XAU`, `XCD`, `XOF`, `XPD`, `XPF`, `XPT`, `XSU`, `YER`, `ZAR`, `ZMK`, `ZWL`. How many units of this currency one unit of the company currency buys, over this date range (the company currency's own rate is 1). To convert between any two currencies of the same range, take the ratio: an amount in the source currency times `rate` of the target divided by `rate` of the source — the formula the app itself applies. What the call did to the range: `created` (a new range), `updated` (some of its rates changed, or it gained a currency), or `unchanged` (it already had these rates; nothing was written). Currencies the range did not carry before, now added at the rate given to every range that lacked them. To set another rate on an older range, call the tool on that range. # update_group Source: https://docs.qobra.co/mcp_documentation/tools/update_group Rename a group, declare its whole membership in one call, or both. Missing rows are removed, so send the full set to keep. Rename a group, set who is in it, or both in one call. Pass at least one of `name`, `key` or `attributions` — a call with none of them has nothing to update. `attributions` is the group's **whole** membership, not an addition to it: whoever the list leaves out is taken out of the group. Read the group with [`get_group`](/mcp_documentation/tools/get_group) first and send back every member to keep, or the ones missing from your list are removed. Omitting the argument leaves the membership untouched, so a rename alone is safe; passing an empty list empties the group. Declaring a membership that already matches changes nothing, so the same call can be sent twice. Removing a membership needs the delete permission on groups, as it does in the web app. Only what actually changed comes back in `attribution_changes`, each row saying whether it was created, moved, or removed. A membership change here recalculates the statements of the users it affects, on the live environment, wherever the group is attributed to a plan — their statements move to `NEED_REFRESH` and are recalculated in the background. A call that changes nothing triggers no refresh. Renaming propagates too: the group reads under its new name on every statement and in every report that breaks down by its dimension. Available only where the Qobra write tools beta is enabled. Every write to the live environment is recorded in the audit trail as the `Qobra MCP` agent; writes to a sandbox are not audited. ## Parameters Id of the group to update — the id list\_groups returns, or the one create\_group just returned. New display name for the group. Omit to leave the current name as it is. New stable key for the group — the identifier an integration matches it on, distinct from the display name. Omit to leave the current key as it is. Who is in the group, as the complete list — every user, each with the months they belong for. This replaces the group's membership rather than adding to it: anyone missing from the list is taken out of the group. Omit the argument to leave the membership untouched; pass an empty list to empty the group. Send one row per continuous stretch, with no month covered twice for the same user. Id or email of the user in the group, as a user reference carries them. YYYY-MM; the first month this membership covers. YYYY-MM; the last month it covers, included. Omit it for a membership that stays open with no end. ## Response The group as it now stands after the update. Deep link to open this group in the Qobra web app. The group's stable key — the identifier used to match it when synced from an integration, distinct from its display name. The dimension the group belongs to (the kind of grouping, e.g. 'Country' or 'Team'). Null if the dimension is unknown. Number of users currently in the group (attributions active as of today). Past and future members are excluded. Every membership the call actually changed — created, moved, or removed because the declared list no longer held it. Empty when the call only renamed the group, and empty when the declared membership already matched what the group had, which is how a repeated call reads as the no-op it was. `created` for a membership this call added; `moved` for one that already existed and had its dates changed, the row showing the new ones; `removed` for one the declared set no longer holds. The membership as it now stands — its last state before removal for a "removed" row, since the user is no longer in the group. # update_plan Source: https://docs.qobra.co/mcp_documentation/tools/update_plan Rename a plan, declare its whole membership in one call, or both. Missing rows are removed, so send the full set to keep. Rename a plan, set who is attributed to it, or both in one call. Pass at least one of `name` or `attributions` — a call with neither has nothing to update. `attributions` is the plan's **whole** membership, not an addition to it: whoever the list leaves out is taken off the plan. Read the plan with [`get_plan`](/mcp_documentation/tools/get_plan) first and send back every membership to keep, or the ones missing from your list are removed. Omitting the argument leaves the membership untouched, so a rename alone is safe; passing an empty list clears the plan. Declaring a membership that already matches changes nothing, so the same call can be sent twice. Only what actually changed comes back in `attribution_changes`, each row saying whether it was created, moved, or removed. On the live environment, changing a membership triggers a statement refresh for every user it affects — their statements move to `NEED_REFRESH` and are recalculated in the background. A call that changes nothing triggers no refresh, and a sandbox write causes none either. Available only where the Qobra write tools beta is enabled. Every write to the live environment is recorded in the audit trail as the `Qobra MCP` agent; writes to a sandbox are not audited. ## Parameters Id of the plan to update — the id list\_plans returns. The plan also decides which environment the attributions land in, so there is no sandbox argument here. New display name for the plan. Omit to leave the current name as it is. Who the plan attributes, as the complete list — every user and group on it, each with the months they are on it for. This replaces the plan's membership rather than adding to it: anyone missing from the list is taken off the plan. Omit the argument to leave the membership untouched; pass an empty list to clear it. Send one row per continuous stretch, with no month covered twice for the same user or group. Id or email of the user to attribute, as a user reference carries them. Fill this or group\_id, never both. Id of the group to attribute — the id list\_groups returns. Fill this or user\_id, never both. YYYY-MM; the first month this membership covers. YYYY-MM; the last month it covers, included. Omit it for a membership that stays open with no end. ## Response The plan as it now stands after the update. Deep link to open this plan in the Qobra web app. The section the plan is filed under; null when unfiled. Every membership the call actually changed — created, moved, or removed because the declared list no longer held it. Empty when the call only renamed the plan, and empty when the declared membership already matched what the plan had, which is how a repeated call reads as the no-op it was. `created` for a membership this call added; `moved` for one that already existed and had its dates changed, the row showing the new ones; `removed` for one the declared set no longer holds. The attribution as it now stands — its last state before removal for a "removed" row, since it no longer exists on the plan. `user` for an individual attribution, `group` for a whole group attributed at once. The attributed user; null for group attributions. The attributed group; null for user attributions. # update_quota_values Source: https://docs.qobra.co/mcp_documentation/tools/update_quota_values Set, change or clear target values of one quota, for one or more users. All or nothing, at most 100 values per call. Set, change or clear target values of one quota, for one or more users. Only the cells listed are written: a user or a period left out keeps its value, so this is an adjustment, never a replacement of the whole quota. Read the current values with list\_quota\_values first when the change is relative to them. Pass `value: null` to clear a cell. On a value synchronized from an integration, `null` cancels the manual overwrite and brings the synchronized value back instead. The call is all or nothing: an unknown user, a period not in the quota's frequency format or a period listed twice refuses the whole call before anything is written. A call writes at most 100 values, all users together. Statements are not recalculated by this call, just as when a target is edited in the web app — they pick the new values up at the next automatic recalculation, or when someone refreshes them. The quota's environment (live or a sandbox) is the one the quota lives in. Requires write permission on quotas. Available only where the Qobra write tools beta is enabled. Every write to the live environment is recorded in the audit trail as the `Qobra MCP` agent; writes to a sandbox are not audited. ## Parameters Id of the quota whose targets to write — the id list\_quotas returns, or the one create\_quota just returned. The targets to write, grouped by user. At most 100 values per call, all users together. Id or email of the user, as a user reference carries them. Currency of this user's amount targets, as a code (EUR, USD…). It applies to every value this user has on the quota, not only to the ones sent here. Omit it to keep the user's current currency; ignored on a percentage or float quota. The cells to write for this user, one per period. Period of the target, in the quota's own frequency format: YYYY-MM (monthly), YYYY-QX (quarterly), YYYY-SX (semesterly) or YYYY (annually). The target, in the unit list\_quota\_values returns. null clears it — on a value synchronized from an integration, null cancels the manual overwrite and brings the synchronized value back instead, since a synchronized value is never deleted. ## Example ```json theme={null} { "quota_id": "66a0b1c2d3e4f50012345678", "values": [ { "user_id": "66a0b1c2d3e4f50012340001", "currency": "EUR", "values": [ { "period": "2026-10", "value": 50000 }, { "period": "2026-11", "value": 55000 } ] }, { "user_id": "66a0b1c2d3e4f50012340002", "values": [{ "period": "2026-10", "value": null }] } ] } ``` ## Response The quota the values were written on — the same row list\_quotas returns. See [`list_quotas`](/mcp_documentation/tools/list_quotas) for the full field breakdown. Its `views` list the view ids list\_quota\_values takes as `view_id`, but not who they are shared with: each view's `access` is null here. Call list\_quotas when the question is who can read a view. Every cell this call changed. A cell sent at the value it already held is not listed, so an empty list means nothing changed. The value after this call; null when the cell was cleared. Currency code of the value, only for an amount quota. Deep link to open the quota in the Qobra web app. What happened to the cell: created (it was empty), updated (new value), cleared (the value was removed), or overwrite\_cancelled (a manual overwrite of a synchronized value was dropped, bringing the synchronized value back). One of: `created`, `updated`, `cleared`, `overwrite_cancelled`. The value before this call; null when the cell was empty. # update_record Source: https://docs.qobra.co/mcp_documentation/tools/update_record Change some cells of one data table record, archive it or restore it. Change some cells of one record, archive it or restore it. Only the fields listed change. Call get\_data\_table first for the ids, the type each value takes and `editable` (false for lookups and links, which cannot be written), and list\_data\_table\_records for the current values. On a record synced from an integration the edit is kept as an overwrite and the synced value is preserved beside it, as in the web app. An import run with the "override" policy replaces the edit with the synced value. Statements are not recalculated by this call, just as when a record is edited in the web app. They pick the change up at the next automatic recalculation, or when someone refreshes them. Requires write permission on data tables. Not available on demonstration companies. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Parameters Id of the data table holding the record — the id list\_data\_table\_records was called with. Id of the record to change, in that table — the id list\_data\_table\_records returns. The cells to change, one per field. A field left out keeps its value; `value: null` empties a cell. Omit to change only `active`. Id of the column — a field id get\_data\_table returns. Only a field whose `editable` is true can be written. The cell's new value, in the shape the read tools serve it: a number for amount / float / percentage (a percentage is the ratio, 0.8 for 80%), true/false for bool, an ISO date for date, text for string, one option label for picklist, a list of option labels for multipicklist, and a user id or email for user. null empties the cell. Currency code of an amount (EUR, USD…). Omit it to keep the cell's currency; ignored on every other type. false archives the record, true restores it. Omit to leave it as it is. Why the record is changed — one of the table's `adjustment_reasons` from get\_data\_table. Required only when that list is not null; omit it otherwise. Optional note kept in the record's history. The call is refused, with a message naming the problem, when no data table has the `data_table_id`, when no record of that table has the `record_id`, when neither `values` nor `active` is passed, when a `field_id` is unknown or listed twice, when a field is a lookup or a link to another table, when a value does not match its field's type or picklist options, or when the table requires a `reason` and none from its `adjustment_reasons` was passed. ## Example ```json theme={null} { "data_table_id": "665f1c2e8a4b3d0012a7e9f1", "record_id": "665f1c2e8a4b3d0012a7ea10", "values": [ { "field_id": "665f1c2e8a4b3d0012a7e9f3", "value": 15000, "currency": "EUR" }, { "field_id": "665f1c2e8a4b3d0012a7e9f4", "value": null } ], "reason": "Amount correction", "comment": "Amount renegotiated after signature." } ``` To archive a record without changing its values, pass only `active`: ```json theme={null} { "data_table_id": "665f1c2e8a4b3d0012a7e9f1", "record_id": "665f1c2e8a4b3d0012a7ea10", "active": false } ``` ## Response The table of the record, whose `fields` header names the record's values in order — the same row list\_data\_tables returns. See [`list_data_tables`](/mcp_documentation/tools/list_data_tables) for the full field breakdown. The record as it now exists — the same row list\_data\_table\_records returns. Its `values` are read in the order of `data_table.fields`. See [`list_data_table_records`](/mcp_documentation/tools/list_data_table_records) for the full field breakdown. What this call changed, the record's archived state included (as 'Active record'). Empty when nothing changed — the call then wrote and recorded nothing. Name of the field that changed. The value before the call, as the record's history shows it. The value after the call, as the record's history shows it. # update_request Source: https://docs.qobra.co/mcp_documentation/tools/update_request Change an existing request's title, status, assignee, topic, members or linked statements, and close or reopen it. Change an existing request: its title, status, assignee, topic, members or linked statements. Pass only what changes: an omitted argument leaves that part as it is, and a call that changes nothing writes nothing. `member_ids` and `statements` are whole sets, not additions — call get\_request first and send back everything to keep, or what is missing from your list is removed. A statement the request already links, sent back with the same record, is kept as it is. A closed request can be edited only in a call that reopens it (`status` open); a call can also make its changes and close the request in one go. The assignee and the topic can be changed here but not removed. Removing yourself from `member_ids` is allowed, and you then lose the request unless your role reaches it another way. Available only where the Qobra write tools beta is enabled. Every write is recorded in the audit trail as the `Qobra MCP` agent. ## Example To add a member and close the request in one call, read the request with [`get_request`](/mcp_documentation/tools/get_request) first, then send back every member and statement to keep alongside the new one: ```json theme={null} { "request_id": "6650f1c2a8b4e1d2c3f4a5b6", "status": "closed", "member_ids": ["6650f1c2a8b4e1d2c3f4a001", "6650f1c2a8b4e1d2c3f4a002"], "statements": [ { "user_id": "jane.doe@example.com", "period": "2026-03" } ] } ``` Omit `member_ids` and `statements` entirely when they don't change. ## Parameters Id of the request to change — the id list\_requests or create\_request returns. New title of the request. Omit to keep it. open to reopen the request, closed to close it as resolved. Omit to keep it. One of: `open`, `closed`. Id or email of the person to hand the request to — as list\_users returns it. They become a member and are notified it is theirs. Omit to keep the assignee. Id of the topic to file the request under — one of `requests.topics` from get\_company\_settings. Omit to keep it. The request's **whole** membership — ids or emails, as list\_users returns them. Anyone left out is removed, anyone new is notified they were added, and the assignee stays a member whatever the list. Omit to leave the members as they are. The **whole** set of statements the request is about — one user in one month each, optionally narrowed to one of its records. A statement left out is unlinked, and an empty list unlinks them all. Omit to leave them as they are. Id or exact email of the user whose statement it is. Month of the statement, as YYYY-MM (e.g. '2026-03'). The one record of this statement the request is about — a deal, most often. Omit to link the statement as a whole. Id of the scope the record was frozen into — the `id` of one of get\_statement\_compensation's scopes. Id of the record — the `record_id` list\_statement\_records returns for that scope. ## Response The request as it now stands — the same row list\_requests returns, unchanged when the call changed nothing. Deep link to open this request in the Qobra web app. The #-style reference shown in the UI. Admin-configured category used to classify the request; null if none. open (awaiting handling) or closed (resolved). One of: `open`, `closed`. The linked deal/record when the statement is record-based; null otherwise.