> For the complete documentation index, see [llms.txt](https://developer.abacum.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.abacum.ai/guides/public-api-guides/export-model-data.md).

# Export model data

Pull actuals, scenario data or working version data from a model.

Use these steps to pull model data into a warehouse or a BI tool. You need a user with the Admin role. See [Access and permissions](/essentials/concepts/access-and-permissions.md).

## Step 1: Find the model

Call `GET /public-api/models`. Each model has an `id`, a `name`, a `code`, and a `timescale`, which is `monthly` or `weekly`. The list leaves out the restricted models that the user cannot access.

## List models

> Retrieve a list of models for the authenticated user’s organization. Restricted models that the user cannot access are left out.

```json
{"openapi":"3.0.0","info":{"title":"Abacum public API","version":"1.0.0"},"tags":[{"name":"Models","description":"List, create, rename and move models, and read their actuals, scenario and rolling data and their logic."}],"servers":[{"url":"https://api.abacum.io","description":"Prod environment"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An access token from the token endpoint, sent as `Authorization: Bearer <access_token>`. Use it to try an endpoint from this page. The token carries the permissions of its user, so use a user with the lowest role that fits the call."}},"responses":{"ForbiddenAdminOnly":{"description":"One of two causes:\n\n- The bearer token is missing, invalid or expired. The response has an empty body.\n- The token's user is not an Admin or Super Admin. The body is `{\"detail\": \"You do not have permission to perform this action.\"}`.\n\nThe API answers `403` for a bad token, not `401`.\n","headers":{"X-Request-ID":{"$ref":"#/components/headers/XRequestID"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailMessage"}}}},"TooManyRequests20PerMinute":{"description":"Rate limit exceeded: 20 requests per minute for the same user and URL. Wait the number of seconds in the `Retry-After` header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before the next request.","schema":{"type":"integer"}},"X-Request-ID":{"$ref":"#/components/headers/XRequestID"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailMessage"}}}},"InternalServerError":{"description":"An unexpected error on our side. The response has an empty body. Repeat the request, and contact support if it keeps failing.","headers":{"X-Request-ID":{"$ref":"#/components/headers/XRequestID"}}}},"headers":{"XRequestID":{"description":"Identifier of this request. Quote it when you contact support so they can find the exact request. A server-side client can set it by sending an `X-Request-ID` request header of 1 to 64 characters from `A-Za-z0-9_-`; any other value is replaced by a generated one. Browsers can read this header but cannot send their own. The value identifies a request for support, but it is not proof that only one request used it.","schema":{"type":"string"}}},"schemas":{"DetailMessage":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}},"paths":{"/public-api/models":{"get":{"summary":"List models","description":"Retrieve a list of models for the authenticated user’s organization. Restricted models that the user cannot access are left out.","operationId":"listModels","tags":["Models"],"responses":{"200":{"description":"A list of models.","content":{"application/json":{"schema":{"type":"object","required":["data","metadata"],"properties":{"data":{"type":"array","items":{"type":"object","required":["id","name","code","timescale","restricted","last_actuals_date","created_at","updated_at"],"properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier of the model. Use it as `model_id` in the other model endpoints."},"name":{"type":"string","description":"Name of the model."},"code":{"type":"string","description":"Stable code of the model."},"timescale":{"type":"string","enum":["monthly","weekly"],"description":"The model's native time grain."},"restricted":{"type":"boolean","description":"Whether the model is restricted."},"last_actuals_date":{"type":"string","format":"date","nullable":true,"description":"Date of the latest actuals. `null` when the model has no actuals."},"created_at":{"type":"string","format":"date-time","description":"When the model was created."},"updated_at":{"type":"string","format":"date-time","description":"When the model was last updated."}}}},"metadata":{"type":"object","description":"Always an empty object."}}}}}},"403":{"$ref":"#/components/responses/ForbiddenAdminOnly"},"429":{"$ref":"#/components/responses/TooManyRequests20PerMinute"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## Step 2: Find the scenario

Call `GET /public-api/scenarios`. Each scenario has an `id`, a `scenario_name`, a `version_name` and a `version_category`, such as `Forecast`. You need a scenario ID for scenario data and working version data. Actuals need no scenario.

## List scenarios

> Retrieve a list of scenarios for the authenticated user’s organization.

```json
{"openapi":"3.0.0","info":{"title":"Abacum public API","version":"1.0.0"},"tags":[{"name":"Scenarios","description":"List the forecast and budget scenarios of your organization."}],"servers":[{"url":"https://api.abacum.io","description":"Prod environment"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An access token from the token endpoint, sent as `Authorization: Bearer <access_token>`. Use it to try an endpoint from this page. The token carries the permissions of its user, so use a user with the lowest role that fits the call."}},"responses":{"ForbiddenAdminOnly":{"description":"One of two causes:\n\n- The bearer token is missing, invalid or expired. The response has an empty body.\n- The token's user is not an Admin or Super Admin. The body is `{\"detail\": \"You do not have permission to perform this action.\"}`.\n\nThe API answers `403` for a bad token, not `401`.\n","headers":{"X-Request-ID":{"$ref":"#/components/headers/XRequestID"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailMessage"}}}},"TooManyRequests20PerMinute":{"description":"Rate limit exceeded: 20 requests per minute for the same user and URL. Wait the number of seconds in the `Retry-After` header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before the next request.","schema":{"type":"integer"}},"X-Request-ID":{"$ref":"#/components/headers/XRequestID"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailMessage"}}}},"InternalServerError":{"description":"An unexpected error on our side. The response has an empty body. Repeat the request, and contact support if it keeps failing.","headers":{"X-Request-ID":{"$ref":"#/components/headers/XRequestID"}}}},"headers":{"XRequestID":{"description":"Identifier of this request. Quote it when you contact support so they can find the exact request. A server-side client can set it by sending an `X-Request-ID` request header of 1 to 64 characters from `A-Za-z0-9_-`; any other value is replaced by a generated one. Browsers can read this header but cannot send their own. The value identifies a request for support, but it is not proof that only one request used it.","schema":{"type":"string"}}},"schemas":{"DetailMessage":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}},"paths":{"/public-api/scenarios":{"get":{"summary":"List scenarios","description":"Retrieve a list of scenarios for the authenticated user’s organization.","operationId":"listScenarios","tags":["Scenarios"],"responses":{"200":{"description":"A list of scenarios.","content":{"application/json":{"schema":{"type":"object","required":["data","metadata"],"properties":{"data":{"type":"array","items":{"type":"object","required":["id","version_id","version_name","version_category","version_active","scenario_name","scenario_default","starts","ends","is_locked","created_at","updated_at"],"properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier of the scenario. Use it as `scenario_id` in the model data endpoints."},"version_id":{"type":"string","format":"uuid","description":"Unique identifier of the version the scenario belongs to."},"version_name":{"type":"string","description":"Name of the version the scenario belongs to."},"version_category":{"type":"string","enum":["Forecast","Budget","Scenario"],"description":"Category of the version."},"version_active":{"type":"boolean","description":"Whether the version is active."},"scenario_name":{"type":"string","description":"Name of the scenario."},"scenario_default":{"type":"boolean","description":"Whether this is the default scenario of its version."},"starts":{"type":"string","format":"date","description":"First day of the version."},"ends":{"type":"string","format":"date","description":"Last day of the version."},"is_locked":{"type":"boolean","description":"Whether the version is locked."},"created_at":{"type":"string","format":"date-time","description":"When the scenario was created."},"updated_at":{"type":"string","format":"date-time","description":"When the scenario was last updated."}}}},"metadata":{"type":"object","description":"Always an empty object."}}}}}},"403":{"$ref":"#/components/responses/ForbiddenAdminOnly"},"429":{"$ref":"#/components/responses/TooManyRequests20PerMinute"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## Step 3: Choose the variables

This step is optional. Call `GET /public-api/variables?model_id={model_id}` to list the variables of one model. Without `model_id`, the response holds every variable that the user can read.

## List variables

> Retrieve a list of variables for the authenticated user's organization. Optionally filter by model.

```json
{"openapi":"3.0.0","info":{"title":"Abacum public API","version":"1.0.0"},"tags":[{"name":"Variables","description":"List the variables of your models."}],"servers":[{"url":"https://api.abacum.io","description":"Prod environment"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An access token from the token endpoint, sent as `Authorization: Bearer <access_token>`. Use it to try an endpoint from this page. The token carries the permissions of its user, so use a user with the lowest role that fits the call."}},"schemas":{"ErrorMessage":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"DetailMessage":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}},"responses":{"TooManyRequests20PerMinute":{"description":"Rate limit exceeded: 20 requests per minute for the same user and URL. Wait the number of seconds in the `Retry-After` header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before the next request.","schema":{"type":"integer"}},"X-Request-ID":{"$ref":"#/components/headers/XRequestID"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailMessage"}}}},"InternalServerError":{"description":"An unexpected error on our side. The response has an empty body. Repeat the request, and contact support if it keeps failing.","headers":{"X-Request-ID":{"$ref":"#/components/headers/XRequestID"}}}},"headers":{"XRequestID":{"description":"Identifier of this request. Quote it when you contact support so they can find the exact request. A server-side client can set it by sending an `X-Request-ID` request header of 1 to 64 characters from `A-Za-z0-9_-`; any other value is replaced by a generated one. Browsers can read this header but cannot send their own. The value identifies a request for support, but it is not proof that only one request used it.","schema":{"type":"string"}}}},"paths":{"/public-api/variables":{"get":{"summary":"List variables","description":"Retrieve a list of variables for the authenticated user's organization. Optionally filter by model.","operationId":"listVariables","tags":["Variables"],"parameters":[{"in":"query","name":"model_id","schema":{"type":"string","format":"uuid"},"required":false,"description":"Filter variables by model ID. Without it, the response lists the variables of every model the user can read."}],"responses":{"200":{"description":"A list of variables.","content":{"application/json":{"schema":{"type":"object","required":["data","metadata"],"properties":{"data":{"type":"array","items":{"type":"object","required":["id","name","description","model_name","model_id","is_ai_priority","dimensions"],"properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier of the variable. Use it in `variable_ids` of the model data endpoints."},"name":{"type":"string","description":"Name of the variable."},"description":{"type":"string","description":"Free-text description of the variable."},"model_name":{"type":"string","description":"Name of the model the variable belongs to."},"model_id":{"type":"string","format":"uuid","description":"Unique identifier of the model the variable belongs to."},"is_ai_priority":{"type":"boolean","description":"Whether the variable is marked as an AI priority."},"dimensions":{"type":"array","items":{"type":"string"},"description":"Names of the dimensions the variable can be broken down by. Use them as the dimensions of a table breakdown. A hierarchy level has the suffix `_level_<n>` (for example `Account_level_2`). The list is empty when the variable has no dimensions."}}}},"metadata":{"type":"object","description":"Always an empty object."}}}}}},"400":{"description":"Invalid request - `model_id` is not a valid UUID. The body is `{\"error\": \"<message>\"}`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorMessage"}}}},"403":{"description":"One of three causes:\n\n- The bearer token is missing, invalid or expired. The response has an empty body.\n- The token's user is not an Admin or Super Admin. The body is `{\"detail\": \"You do not have permission to perform this action.\"}`.\n- The model in `model_id` is restricted and the user cannot access it. The response has an empty body.\n\nThe API answers `403` for a bad token, not `401`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailMessage"}}}},"404":{"description":"Model not found - the specified model_id does not exist or does not belong to the user's organization. The response has an empty body."},"429":{"$ref":"#/components/responses/TooManyRequests20PerMinute"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## Step 4: Request the data

| Endpoint                                                        | Returns                                  |
| --------------------------------------------------------------- | ---------------------------------------- |
| `GET /public-api/models/{model_id}/actuals_data`                | Actual values                            |
| `GET /public-api/models/{model_id}/scenario_data/{scenario_id}` | The values of one scenario               |
| `GET /public-api/models/{model_id}/rolling_data/{scenario_id}`  | The working version data of one scenario |

## Get model actuals data

> Retrieve actuals data for the requested model.

```json
{"openapi":"3.0.0","info":{"title":"Abacum public API","version":"1.0.0"},"tags":[{"name":"Models","description":"List, create, rename and move models, and read their actuals, scenario and rolling data and their logic."}],"servers":[{"url":"https://api.abacum.io","description":"Prod environment"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An access token from the token endpoint, sent as `Authorization: Bearer <access_token>`. Use it to try an endpoint from this page. The token carries the permissions of its user, so use a user with the lowest role that fits the call."}},"parameters":{"ModelId":{"name":"model_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"The ID of the model. It must be a UUID. Any other value returns a `404` with an HTML page."},"StartDate":{"name":"start_date","in":"query","required":false,"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}(-\\d{2})?$"},"description":"Start of the period. Accepts `YYYY-MM` (monthly models only) or `YYYY-MM-DD`. The period is snapped to the model's grain: monthly models expand a month to its first day; weekly models snap to the week start."},"EndDate":{"name":"end_date","in":"query","required":false,"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}(-\\d{2})?$"},"description":"End of the period. Accepts `YYYY-MM` (monthly models only) or `YYYY-MM-DD`. The period is snapped to the model's grain: monthly models expand a month to its last day; weekly models snap to the week end."},"Enrich":{"name":"enrich","in":"query","required":false,"schema":{"type":"boolean"},"description":"Replace variable IDs with variable names, and dimension names with labels. In `metadata.properties`, the `variable_id` entry is then named `variable_name`, and the keys of its `variables` map are variable names."},"VariableIds":{"name":"variable_ids","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated list of variable IDs to filter the data. Only data for the specified variables will be returned."},"OutputFormat":{"name":"output_format","in":"query","required":false,"schema":{"type":"string","enum":["csv","parquet"],"default":"csv"},"description":"Output file format. `csv` returns a presigned URL (30 min TTL); `parquet` returns a presigned URL (2 h TTL)."},"Timescale":{"name":"timescale","in":"query","required":false,"schema":{"type":"string","enum":["monthly","quarterly","yearly","full"]},"description":"Time grain of the returned data. Defaults to the model's own grain. It must be the model's grain or coarser, otherwise the request fails with `400`. `full` aggregates the whole requested period into one value per variable, and the file and `metadata.properties` have no time column. On a weekly model, `metadata.properties` also has a `fiscal_week` entry."},"Dimensions":{"name":"dimensions","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated list of dimension codes. The data is broken down by these dimensions only, and the others are rolled up. Every returned variable must have all of them, otherwise the request fails with `400`. Omit the parameter to keep each variable's own dimensions. Pass an empty value (`dimensions=`) to remove all dimensions."}},"schemas":{"ModelDataColumns":{"type":"object","description":"Describes the columns of the data file, keyed by column name.","required":["value"],"properties":{"variable_id":{"$ref":"#/components/schemas/ModelDataVariableColumn"},"variable_name":{"$ref":"#/components/schemas/ModelDataVariableColumn"},"time":{"type":"object","required":["type","format"],"properties":{"type":{"type":"string","enum":["string"]},"format":{"type":"string","enum":["date"]}}},"value":{"type":"object","required":["type","format"],"properties":{"type":{"type":"string","enum":["number"]},"format":{"type":"string","enum":["float"]}}}},"additionalProperties":{"type":"object","required":["type"],"properties":{"type":{"type":"string"},"name":{"type":"string"},"label":{"type":"string"}}}},"ModelDataVariableColumn":{"type":"object","description":"The column that holds the variable. It is named `variable_id`, or `variable_name` when `enrich=true`.","required":["type","variables"],"properties":{"type":{"type":"string","enum":["string"]},"variables":{"type":"object","description":"The variables in the file, keyed by variable ID, or by variable name when `enrich=true`.","additionalProperties":{"type":"object","required":["id","name","dimensions"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"dimensions":{"type":"array","items":{"type":"string"}}}}}}},"ErrorMessage":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"DetailMessage":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}},"responses":{"ForbiddenAdminOnly":{"description":"One of two causes:\n\n- The bearer token is missing, invalid or expired. The response has an empty body.\n- The token's user is not an Admin or Super Admin. The body is `{\"detail\": \"You do not have permission to perform this action.\"}`.\n\nThe API answers `403` for a bad token, not `401`.\n","headers":{"X-Request-ID":{"$ref":"#/components/headers/XRequestID"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailMessage"}}}},"TooManyRequests10PerMinute":{"description":"Rate limit exceeded: 10 requests per minute for the same user and URL. Wait the number of seconds in the `Retry-After` header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before the next request.","schema":{"type":"integer"}},"X-Request-ID":{"$ref":"#/components/headers/XRequestID"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailMessage"}}}},"InternalServerError":{"description":"An unexpected error on our side. The response has an empty body. Repeat the request, and contact support if it keeps failing.","headers":{"X-Request-ID":{"$ref":"#/components/headers/XRequestID"}}}},"headers":{"XRequestID":{"description":"Identifier of this request. Quote it when you contact support so they can find the exact request. A server-side client can set it by sending an `X-Request-ID` request header of 1 to 64 characters from `A-Za-z0-9_-`; any other value is replaced by a generated one. Browsers can read this header but cannot send their own. The value identifies a request for support, but it is not proof that only one request used it.","schema":{"type":"string"}}}},"paths":{"/public-api/models/{model_id}/actuals_data":{"get":{"summary":"Get model actuals data","description":"Retrieve actuals data for the requested model.","operationId":"getModelActualsData","tags":["Models"],"parameters":[{"$ref":"#/components/parameters/ModelId"},{"$ref":"#/components/parameters/StartDate"},{"$ref":"#/components/parameters/EndDate"},{"$ref":"#/components/parameters/Enrich"},{"$ref":"#/components/parameters/VariableIds"},{"$ref":"#/components/parameters/OutputFormat"},{"$ref":"#/components/parameters/Timescale"},{"$ref":"#/components/parameters/Dimensions"}],"responses":{"200":{"description":"A download link to a file (CSV or Parquet, per `output_format`) containing actuals data for the Model.","content":{"application/json":{"schema":{"type":"object","required":["data","metadata"],"properties":{"data":{"type":"string","description":"Pre-signed S3 URL for downloading the data file."},"metadata":{"type":"object","required":["properties"],"properties":{"properties":{"$ref":"#/components/schemas/ModelDataColumns"}}}}}}}},"400":{"description":"Invalid request. The body is `{\"error\": \"<message>\"}`. Causes: an invalid parameter value, `start_date` and `end_date` not provided together, a month-level (`YYYY-MM`) filter on a weekly model, a `timescale` finer than the model's grain, `variable_ids` that are not in the model, `dimensions` that a returned variable does not have, or a result too large to return.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorMessage"}}}},"403":{"$ref":"#/components/responses/ForbiddenAdminOnly"},"404":{"description":"Not found - the model does not exist or the user cannot access it. The response has an empty body."},"429":{"$ref":"#/components/responses/TooManyRequests10PerMinute"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## Get model scenario data

> Retrieve model scenario data for the requested model and scenario.

```json
{"openapi":"3.0.0","info":{"title":"Abacum public API","version":"1.0.0"},"tags":[{"name":"Models","description":"List, create, rename and move models, and read their actuals, scenario and rolling data and their logic."}],"servers":[{"url":"https://api.abacum.io","description":"Prod environment"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An access token from the token endpoint, sent as `Authorization: Bearer <access_token>`. Use it to try an endpoint from this page. The token carries the permissions of its user, so use a user with the lowest role that fits the call."}},"parameters":{"ModelId":{"name":"model_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"The ID of the model. It must be a UUID. Any other value returns a `404` with an HTML page."},"ScenarioId":{"name":"scenario_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"The ID of the scenario. It must be a UUID. Any other value returns a `404` with an HTML page."},"StartDate":{"name":"start_date","in":"query","required":false,"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}(-\\d{2})?$"},"description":"Start of the period. Accepts `YYYY-MM` (monthly models only) or `YYYY-MM-DD`. The period is snapped to the model's grain: monthly models expand a month to its first day; weekly models snap to the week start."},"EndDate":{"name":"end_date","in":"query","required":false,"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}(-\\d{2})?$"},"description":"End of the period. Accepts `YYYY-MM` (monthly models only) or `YYYY-MM-DD`. The period is snapped to the model's grain: monthly models expand a month to its last day; weekly models snap to the week end."},"Enrich":{"name":"enrich","in":"query","required":false,"schema":{"type":"boolean"},"description":"Replace variable IDs with variable names, and dimension names with labels. In `metadata.properties`, the `variable_id` entry is then named `variable_name`, and the keys of its `variables` map are variable names."},"VariableIds":{"name":"variable_ids","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated list of variable IDs to filter the data. Only data for the specified variables will be returned."},"OutputFormat":{"name":"output_format","in":"query","required":false,"schema":{"type":"string","enum":["csv","parquet"],"default":"csv"},"description":"Output file format. `csv` returns a presigned URL (30 min TTL); `parquet` returns a presigned URL (2 h TTL)."},"Timescale":{"name":"timescale","in":"query","required":false,"schema":{"type":"string","enum":["monthly","quarterly","yearly","full"]},"description":"Time grain of the returned data. Defaults to the model's own grain. It must be the model's grain or coarser, otherwise the request fails with `400`. `full` aggregates the whole requested period into one value per variable, and the file and `metadata.properties` have no time column. On a weekly model, `metadata.properties` also has a `fiscal_week` entry."},"Dimensions":{"name":"dimensions","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated list of dimension codes. The data is broken down by these dimensions only, and the others are rolled up. Every returned variable must have all of them, otherwise the request fails with `400`. Omit the parameter to keep each variable's own dimensions. Pass an empty value (`dimensions=`) to remove all dimensions."}},"schemas":{"ModelDataColumns":{"type":"object","description":"Describes the columns of the data file, keyed by column name.","required":["value"],"properties":{"variable_id":{"$ref":"#/components/schemas/ModelDataVariableColumn"},"variable_name":{"$ref":"#/components/schemas/ModelDataVariableColumn"},"time":{"type":"object","required":["type","format"],"properties":{"type":{"type":"string","enum":["string"]},"format":{"type":"string","enum":["date"]}}},"value":{"type":"object","required":["type","format"],"properties":{"type":{"type":"string","enum":["number"]},"format":{"type":"string","enum":["float"]}}}},"additionalProperties":{"type":"object","required":["type"],"properties":{"type":{"type":"string"},"name":{"type":"string"},"label":{"type":"string"}}}},"ModelDataVariableColumn":{"type":"object","description":"The column that holds the variable. It is named `variable_id`, or `variable_name` when `enrich=true`.","required":["type","variables"],"properties":{"type":{"type":"string","enum":["string"]},"variables":{"type":"object","description":"The variables in the file, keyed by variable ID, or by variable name when `enrich=true`.","additionalProperties":{"type":"object","required":["id","name","dimensions"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"dimensions":{"type":"array","items":{"type":"string"}}}}}}},"ErrorMessage":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"DetailMessage":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}},"responses":{"ForbiddenAdminOnly":{"description":"One of two causes:\n\n- The bearer token is missing, invalid or expired. The response has an empty body.\n- The token's user is not an Admin or Super Admin. The body is `{\"detail\": \"You do not have permission to perform this action.\"}`.\n\nThe API answers `403` for a bad token, not `401`.\n","headers":{"X-Request-ID":{"$ref":"#/components/headers/XRequestID"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailMessage"}}}},"TooManyRequests10PerMinute":{"description":"Rate limit exceeded: 10 requests per minute for the same user and URL. Wait the number of seconds in the `Retry-After` header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before the next request.","schema":{"type":"integer"}},"X-Request-ID":{"$ref":"#/components/headers/XRequestID"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailMessage"}}}},"InternalServerError":{"description":"An unexpected error on our side. The response has an empty body. Repeat the request, and contact support if it keeps failing.","headers":{"X-Request-ID":{"$ref":"#/components/headers/XRequestID"}}}},"headers":{"XRequestID":{"description":"Identifier of this request. Quote it when you contact support so they can find the exact request. A server-side client can set it by sending an `X-Request-ID` request header of 1 to 64 characters from `A-Za-z0-9_-`; any other value is replaced by a generated one. Browsers can read this header but cannot send their own. The value identifies a request for support, but it is not proof that only one request used it.","schema":{"type":"string"}}}},"paths":{"/public-api/models/{model_id}/scenario_data/{scenario_id}":{"get":{"summary":"Get model scenario data","description":"Retrieve model scenario data for the requested model and scenario.","operationId":"getModelScenarioData","tags":["Models"],"parameters":[{"$ref":"#/components/parameters/ModelId"},{"$ref":"#/components/parameters/ScenarioId"},{"$ref":"#/components/parameters/StartDate"},{"$ref":"#/components/parameters/EndDate"},{"$ref":"#/components/parameters/Enrich"},{"$ref":"#/components/parameters/VariableIds"},{"$ref":"#/components/parameters/OutputFormat"},{"$ref":"#/components/parameters/Timescale"},{"$ref":"#/components/parameters/Dimensions"}],"responses":{"200":{"description":"A download link to a file (CSV or Parquet, per `output_format`) containing scenario data for the Model.","content":{"application/json":{"schema":{"type":"object","required":["data","metadata"],"properties":{"data":{"type":"string","description":"Pre-signed S3 URL for downloading the data file."},"metadata":{"type":"object","required":["properties","switch_over_date","estimate"],"properties":{"properties":{"$ref":"#/components/schemas/ModelDataColumns"},"switch_over_date":{"type":"string","format":"date","nullable":true,"description":"The version's switchover date: the point where it transitions from actuals to version data. Months up to and including this date are actuals; later months are version data."},"estimate":{"type":"boolean","description":"Whether the values are an estimate — actuals up to `switch_over_date` combined with version data after it, as the app shows them. `false` means the version's formulas were evaluated for every month in range, so months up to `switch_over_date` will not match the app."}}}}}}}},"400":{"description":"Invalid request. The body is `{\"error\": \"<message>\"}`. Causes: an invalid parameter value, `start_date` and `end_date` not provided together, a month-level (`YYYY-MM`) filter on a weekly model, a `timescale` finer than the model's grain, `variable_ids` that are not in the model, `dimensions` that a returned variable does not have, or a result too large to return.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorMessage"}}}},"403":{"$ref":"#/components/responses/ForbiddenAdminOnly"},"404":{"description":"Not found - the model or scenario does not exist, or the user cannot access it. The response has an empty body."},"429":{"$ref":"#/components/responses/TooManyRequests10PerMinute"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## Get model rolling data

> Retrieve rolling data for the requested model and scenario. Combining actuals and scenario data.

```json
{"openapi":"3.0.0","info":{"title":"Abacum public API","version":"1.0.0"},"tags":[{"name":"Models","description":"List, create, rename and move models, and read their actuals, scenario and rolling data and their logic."}],"servers":[{"url":"https://api.abacum.io","description":"Prod environment"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An access token from the token endpoint, sent as `Authorization: Bearer <access_token>`. Use it to try an endpoint from this page. The token carries the permissions of its user, so use a user with the lowest role that fits the call."}},"parameters":{"ModelId":{"name":"model_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"The ID of the model. It must be a UUID. Any other value returns a `404` with an HTML page."},"ScenarioId":{"name":"scenario_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"The ID of the scenario. It must be a UUID. Any other value returns a `404` with an HTML page."},"StartDate":{"name":"start_date","in":"query","required":false,"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}(-\\d{2})?$"},"description":"Start of the period. Accepts `YYYY-MM` (monthly models only) or `YYYY-MM-DD`. The period is snapped to the model's grain: monthly models expand a month to its first day; weekly models snap to the week start."},"EndDate":{"name":"end_date","in":"query","required":false,"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}(-\\d{2})?$"},"description":"End of the period. Accepts `YYYY-MM` (monthly models only) or `YYYY-MM-DD`. The period is snapped to the model's grain: monthly models expand a month to its last day; weekly models snap to the week end."},"Enrich":{"name":"enrich","in":"query","required":false,"schema":{"type":"boolean"},"description":"Replace variable IDs with variable names, and dimension names with labels. In `metadata.properties`, the `variable_id` entry is then named `variable_name`, and the keys of its `variables` map are variable names."},"VariableIds":{"name":"variable_ids","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated list of variable IDs to filter the data. Only data for the specified variables will be returned."},"OutputFormat":{"name":"output_format","in":"query","required":false,"schema":{"type":"string","enum":["csv","parquet"],"default":"csv"},"description":"Output file format. `csv` returns a presigned URL (30 min TTL); `parquet` returns a presigned URL (2 h TTL)."},"Timescale":{"name":"timescale","in":"query","required":false,"schema":{"type":"string","enum":["monthly","quarterly","yearly","full"]},"description":"Time grain of the returned data. Defaults to the model's own grain. It must be the model's grain or coarser, otherwise the request fails with `400`. `full` aggregates the whole requested period into one value per variable, and the file and `metadata.properties` have no time column. On a weekly model, `metadata.properties` also has a `fiscal_week` entry."},"Dimensions":{"name":"dimensions","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated list of dimension codes. The data is broken down by these dimensions only, and the others are rolled up. Every returned variable must have all of them, otherwise the request fails with `400`. Omit the parameter to keep each variable's own dimensions. Pass an empty value (`dimensions=`) to remove all dimensions."}},"schemas":{"ModelDataColumns":{"type":"object","description":"Describes the columns of the data file, keyed by column name.","required":["value"],"properties":{"variable_id":{"$ref":"#/components/schemas/ModelDataVariableColumn"},"variable_name":{"$ref":"#/components/schemas/ModelDataVariableColumn"},"time":{"type":"object","required":["type","format"],"properties":{"type":{"type":"string","enum":["string"]},"format":{"type":"string","enum":["date"]}}},"value":{"type":"object","required":["type","format"],"properties":{"type":{"type":"string","enum":["number"]},"format":{"type":"string","enum":["float"]}}}},"additionalProperties":{"type":"object","required":["type"],"properties":{"type":{"type":"string"},"name":{"type":"string"},"label":{"type":"string"}}}},"ModelDataVariableColumn":{"type":"object","description":"The column that holds the variable. It is named `variable_id`, or `variable_name` when `enrich=true`.","required":["type","variables"],"properties":{"type":{"type":"string","enum":["string"]},"variables":{"type":"object","description":"The variables in the file, keyed by variable ID, or by variable name when `enrich=true`.","additionalProperties":{"type":"object","required":["id","name","dimensions"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"dimensions":{"type":"array","items":{"type":"string"}}}}}}},"ErrorMessage":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"DetailMessage":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}},"responses":{"ForbiddenAdminOnly":{"description":"One of two causes:\n\n- The bearer token is missing, invalid or expired. The response has an empty body.\n- The token's user is not an Admin or Super Admin. The body is `{\"detail\": \"You do not have permission to perform this action.\"}`.\n\nThe API answers `403` for a bad token, not `401`.\n","headers":{"X-Request-ID":{"$ref":"#/components/headers/XRequestID"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailMessage"}}}},"TooManyRequests10PerMinute":{"description":"Rate limit exceeded: 10 requests per minute for the same user and URL. Wait the number of seconds in the `Retry-After` header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before the next request.","schema":{"type":"integer"}},"X-Request-ID":{"$ref":"#/components/headers/XRequestID"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailMessage"}}}},"InternalServerError":{"description":"An unexpected error on our side. The response has an empty body. Repeat the request, and contact support if it keeps failing.","headers":{"X-Request-ID":{"$ref":"#/components/headers/XRequestID"}}}},"headers":{"XRequestID":{"description":"Identifier of this request. Quote it when you contact support so they can find the exact request. A server-side client can set it by sending an `X-Request-ID` request header of 1 to 64 characters from `A-Za-z0-9_-`; any other value is replaced by a generated one. Browsers can read this header but cannot send their own. The value identifies a request for support, but it is not proof that only one request used it.","schema":{"type":"string"}}}},"paths":{"/public-api/models/{model_id}/rolling_data/{scenario_id}":{"get":{"summary":"Get model rolling data","description":"Retrieve rolling data for the requested model and scenario. Combining actuals and scenario data.","operationId":"getModelRollingData","tags":["Models"],"parameters":[{"$ref":"#/components/parameters/ModelId"},{"$ref":"#/components/parameters/ScenarioId"},{"$ref":"#/components/parameters/StartDate"},{"$ref":"#/components/parameters/EndDate"},{"$ref":"#/components/parameters/Enrich"},{"$ref":"#/components/parameters/VariableIds"},{"$ref":"#/components/parameters/OutputFormat"},{"$ref":"#/components/parameters/Timescale"},{"$ref":"#/components/parameters/Dimensions"}],"responses":{"200":{"description":"A download link to a file (CSV or Parquet, per `output_format`) containing rolling data for the Model.","content":{"application/json":{"schema":{"type":"object","required":["data","metadata"],"properties":{"data":{"type":"string","description":"Pre-signed S3 URL for downloading the data file."},"metadata":{"type":"object","required":["properties","switch_over_date","estimate"],"properties":{"properties":{"$ref":"#/components/schemas/ModelDataColumns"},"switch_over_date":{"type":"string","format":"date","nullable":true,"description":"The version's switchover date: the point where it transitions from actuals to version data. Months up to and including this date are actuals; later months are version data."},"estimate":{"type":"boolean","description":"Whether the values are an estimate — actuals up to `switch_over_date` combined with version data after it, as the app shows them. `false` means the version's formulas were evaluated for every month in range, so months up to `switch_over_date` will not match the app."}}}}}}}},"400":{"description":"Invalid request. The body is `{\"error\": \"<message>\"}`. Causes: an invalid parameter value, `start_date` and `end_date` not provided together, a month-level (`YYYY-MM`) filter on a weekly model, a `timescale` finer than the model's grain, `variable_ids` that are not in the model, `dimensions` that a returned variable does not have, or a result too large to return.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorMessage"}}}},"403":{"$ref":"#/components/responses/ForbiddenAdminOnly"},"404":{"description":"Not found - the model or scenario does not exist, or the user cannot access it. The response has an empty body."},"429":{"$ref":"#/components/responses/TooManyRequests10PerMinute"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## Step 5: Download the file

The response holds a download link in `data`. Download the file at once. The link lasts a short time. See [Download links](/essentials/concepts/download-links.md).

## Parameters

All parameters are optional.

| Parameter                | Values                                                                                                                                                                                            | Default                        |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
| `start_date`, `end_date` | `YYYY-MM` or `YYYY-MM-DD`. See [Filter by period](/essentials/concepts/filter-by-period.md).                                                                                                      | -                              |
| `enrich`                 | `true` replaces variable IDs with variable names, and dimension names with labels                                                                                                                 | -                              |
| `variable_ids`           | A comma separated list of variable IDs                                                                                                                                                            | Every variable                 |
| `dimensions`             | A comma separated list of dimension codes. The data is broken down by these dimensions only. Every returned variable must have all of them. An empty value, `dimensions=`, removes all dimensions | Each variable's own dimensions |
| `timescale`              | `monthly`, `quarterly`, `yearly`, `full`. It must be the grain of the model or coarser. `full` gives one value for each variable over the whole period, and the file has no `time` column         | The grain of the model         |
| `output_format`          | `csv`, `parquet`                                                                                                                                                                                  | `csv`                          |

The file columns are on [Data file format](/essentials/concepts/data-file-format.md).

## Large exports

{% hint style="warning" %}
The API builds the full file before it returns the link, so one call has an upper size limit. An unfiltered call on a wide model can pass that limit. The API then returns `400` with this body:

`{ "error": "The requested data is too large to retrieve. Narrow the request by period, variables or dimensions." }`
{% endhint %}

Most calls stay well inside the limit and need no filter. If you get this error, narrow the call with a period, `variable_ids` or `dimensions`, then send it again.

Allow for large requests in your client timeout. To keep calls fast, filter by period, variables or dimensions.
