diff --git a/docs/APIContract.md b/docs/APIContract.md
new file mode 100644
index 0000000000..84278f69b9
--- /dev/null
+++ b/docs/APIContract.md
@@ -0,0 +1,134 @@
+# Versions
+
+So far there is only one version
+
+The base URL for an API should be: _baseURL_/_apiVersion_/ meaning even something such as sometestbuildsforpoe.gg/something/a/b/c/ would be valid as a base URL, as long as it has afterwards the per version required endpoints.
+
+## Metadata Endpoint
+
+In order to provide a consistent API a metadata endpoint should be provided under _baseURL_/.well-known/pathofbuilding this endpoint should provide information about the API and the version of the API.
+Furthermore this may allow having custom endpoints for the API.
+
+Specification
+| Feature | Field | Type | Description |
+| ---------------- | ------------- | ---------------- | ------------------------------------------------------------------------------------ |
+| League Filtering | league_filter | bool | This can be used to indicate whether the API supports filtering based on leagues |
+| Gem Filtering | gem_filter | bool | This can be used to indicate whether the API supports filtering based on gems |
+| Streams | streams | StreamInfo[] | A list of streams available to be queried against |
+| Update Builds | update_builds | UpdateBuildsInfo | Indicates if the API supports updating builds via PoB and which fields are supported |
+
+
+
+### Types
+
+StreamInfo
+
+| Field | Type | Description |
+| ------- | ------ | ------------------------------- |
+| name | string | Name of the stream |
+| apiPath | string | API path to the stream endpoint |
+
+apiPath might be changed to a generic endpoint such as `/v1/{stream}/builds`
+
+
+UpdateBuildsInfo
+| Field | Type | Description |
+| ---------- | -------- | ------------------------------------------------------ |
+| hasSupport | bool | indicates if the API supports updating external builds |
+| fields | string[] | list of fields that can be updated |
+
+Example:
+
+```json
+{
+ "hasSupport": true,
+ "fields": ["description", "youtubeUrl"]
+}
+```
+
+
+## Version 1
+
+### Response and Request types
+
+BuildInfo
+
+| Field | Type | Description |
+| ------------ | ------- | ----------------------------------- |
+| pobdata | string | The Path of Building data |
+| name | string | The name of the build |
+| lastModified | integer | The last modification timestamp |
+| buildId | string | The unique identifier for the build |
+
+
+### Endpoints
+
+
+ GET
/v1/builds
(Lists multiple builds)
+
+#### Parameters
+> Query Parameters
+
+| name | type | data type | description |
+| ------ | -------- | --------- | --------------------------------------- |
+| page | optional | integer | Used for pagination |
+| league | optional | string | Used to limit builds to a league |
+| gems | optional | string | Used to limit builds with specific gems |
+
+##### League
+
+These values will just be the base patch version for the league.
+
+These links are generally available via poewiki see: https://www.poewiki.net/wiki/Necropolis_league _Official Page_.
+
+Example values:
+
+| Patch | League | value | url |
+| ----- | ---------------------- | ----- | -------------------------------------- |
+| 3.25 | Settlers of Kalguur | 3.25 | https://www.pathofexile.com/settlers |
+| 3.24 | Necropolis | 3.24 | https://www.pathofexile.com/necropolis |
+| 3.23 | Affliction | 3.23 | https://www.pathofexile.com/affliction |
+| 3.22 | Trial of the Ancestors | 3.22 | https://www.pathofexile.com/ancestor |
+| 3.4 | Delve | 3.4 | https://www.pathofexile.com/delve |
+
+#### Responses
+
+| http code | content-type | response |
+| --------- | ------------------ | ------------------ |
+| `200` | `application/json` | `BuildInfo object` |
+
+
+
+
+
+ POST
/v1/builds/{buildId}
(Update a single build on the source)
+
+#### Parameters
+> Path Parameters
+
+| name | type | data type | description |
+| ------- | -------- | --------- | ---------------------------------------------------------- |
+| buildId | required | string | The build in question on the source that should be updated |
+
+#### Request
+
+| Field | Type | Description |
+| ---------- | -------- | ---------------------------------- |
+| pobdata | string | The Path of Building data |
+| name | string | The name of the build |
+| customData | string[] | A list of custom data to be stored |
+
+customData will be describable fields via the metadata endpoint, if customData is empty it is expected to be ignored and no changes should be made.
+
+#### Responses
+
+| http code | content-type | response | Description |
+| --------- | -------------------------- | --------------------- | ---------------------- |
+| `200` | `application/json` | None | Update succesful |
+| `201` | `application/json` | None | Succesful creation |
+| `400` | `text/html; charset=utf-8` | `Reason for failure` | Input may be incorrect |
+| `401` | `text/html; charset=utf-8` | `Reason if necessary` | Auth incorrect |
+| `404` | `text/html; charset=utf-8` | None | Build does not exist |
+
+
+
diff --git a/src/Classes/APIContractBuilds.lua b/src/Classes/APIContractBuilds.lua
new file mode 100644
index 0000000000..2d734bec38
--- /dev/null
+++ b/src/Classes/APIContractBuilds.lua
@@ -0,0 +1,203 @@
+-- Path of Building
+--
+-- Class: API Contract Builds
+-- API contract proposal for builds
+--
+
+local dkjson = require "dkjson"
+
+
+---@enum EndpointType
+local EndpointType = {
+ REST = 0,
+ CSV = 1
+}
+
+--example for REST vs CSV
+-- REST:
+-- {"baseUrl": "https://mypoebuilds.gg/pob-api/", "name": "MyPoEBuilds REST", "endpointType": 0, "fallbackVersion": 1}
+-- baseUrl meaning the actual baseUrl
+-- CSV:
+-- {"baseUrl": "https://mypoebuilds.gg/pob-builds.csv", "name": "MyPoEBuilds CSV", "endpointType": 1, "fallbackVersion": 1}
+-- baseUrl means here the full URL to the actual file or endpoint that returns a CSV formatted file from a GET endpoint
+
+--- APISourceInfo is the configurated data for a source from the enduser
+---@class APISourceInfo
+---@field name string
+---@field baseUrl string
+---@field endpointType EndpointType
+---@field fallbackVersion integer
+
+--- This data should be returned by the source
+---@class BuildInfo
+---@field pobdata string
+---@field name string
+---@field lastModified integer
+---@field buildId string
+
+--- This data is saved internally for caching
+--- Save this to disk for temporary local caching if required/desired
+---@class BuildInfoCache
+---@field pobdata string
+---@field name string
+---@field lastModified integer
+---@field buildId string
+---@field sourceName string
+
+--- API Capabilities returned by the source or defaulted to APISourceInfo
+---@class APICapabilities
+---@field name string
+---@field fallbackVersion integer
+---@field endpointType EndpointType
+---@field supportedVersion? integer
+---@field baseAPIPath? string
+---@field league_filter? boolean
+---@field gem_filter? boolean
+
+--- Build version 1 Filter option
+---@class BuildVersion1Filter
+---@field league string
+---@field gem string
+
+--- This primarily exists for the lua language server
+---@param buildInfo BuildInfo
+---@param source APICapabilities
+---@return BuildInfoCache
+local function buildInfoToCache(buildInfo, source)
+ return {
+ pobdata = buildInfo.pobdata,
+ name = buildInfo.name,
+ lastModified = buildInfo.lastModified,
+ buildId = buildInfo.buildId,
+ sourceName = source.name
+ }
+end
+
+---@param t table
+local function tableToQueryParams(t)
+ if #t == 0 then
+ return ""
+ end
+ local query = "?"
+ for key, value in pairs(t) do
+ if value then
+ local tempValue = value
+ if type(value) == "table" then
+ tempValue = table.concat(value, ",")
+ end
+ query = query .. key .. "=" .. value .. "&"
+ end
+ end
+ -- remove trailing &
+ if #t > 0 then
+ query = query:sub(1, #query - 1)
+ end
+
+ return query
+end
+
+---@class APIContractBuilds
+---@field buildList table
+---@field apiCapabilities table
+local APIContractBuilds = newClass("APIContractBuilds",
+ function(self)
+ self.buildList = {}
+ self.apiCapabilities = {}
+ end
+)
+
+-- Switch case for GET Endpoint for Builds
+local getBuildVersions = {
+ [1] = function(...) return APIContractBuilds:GetBuildsVersion1(...) end,
+}
+-- Switch case for GET Endpoint for Builds
+local getBuildFilterVersions = {
+ [1] = function(data) return APIContractBuilds:GetBuildVersion1Filter(data) end,
+}
+
+---@param data table
+function APIContractBuilds:GetBuildVersion1Filter(data)
+ return {
+ league = data.league,
+ gem = data.gem
+ }
+end
+
+--- Gets the builds from the source
+---@param source APICapabilities
+---@param data table
+function APIContractBuilds:GetBuilds(source, data)
+ local getBuildsFunction = nil
+ local getBuildsFilterFunction = nil
+ if not source.supportedVersion then
+ getBuildsFunction = getBuildVersions[source.fallbackVersion]
+ getBuildsFilterFunction = getBuildFilterVersions[source.fallbackVersion]
+ else
+ getBuildsFunction = getBuildVersions[source.supportedVersion]
+ getBuildsFilterFunction = getBuildFilterVersions[source.supportedVersion]
+ end
+
+ if getBuildsFunction and getBuildsFilterFunction then
+ getBuildsFunction(source.endpointType, getBuildsFilterFunction(data), source)
+ end
+end
+
+---@param source APISourceInfo
+function APIContractBuilds:UpdateAPICapabilities(source)
+ -- TODO: Which features are supported?
+ -- What is the latest version that is supported?
+ -- Which endpoints are supported
+ -- Is Querying supported? (CSV won't support this probably)
+
+ if source.endpointType ~= EndpointType.REST then
+ return
+ end
+
+ launch:DownloadPage(source.baseUrl + "/.well-known/pathofbuilding",
+ function(...) return APIContractBuilds:APICapabilitiesCallback(source, ...) end, {})
+end
+
+--- Updates the API capabilities for the source
+---@param response table
+---@param errMsg any
+---@param source APISourceInfo
+function APIContractBuilds:APICapabilitiesCallback(source, response, errMsg)
+ local parsedResponse = dkjson.decode(response.body)
+ ---@cast parsedResponse APICapabilities
+ if errMsg or not parsedResponse.baseAPIPath then
+ parsedResponse.baseAPIPath = source.baseUrl
+ end
+ parsedResponse.name = source.name
+ parsedResponse.fallbackVersion = source.fallbackVersion
+ parsedResponse.endpointType = source.endpointType
+
+ self.apiCapabilities[source.name] = parsedResponse
+end
+
+--- Adds/Updates Builds for the source based on version 1
+---@param response table
+---@param errMsg any
+---@param source APICapabilities
+function APIContractBuilds:BuildsVersion1Callback(source, response, errMsg)
+ local parsedResponse = dkjson.decode(response.body)
+ ---@cast parsedResponse BuildInfo[]
+ for _, build in ipairs(parsedResponse) do
+ -- source and buildId will be used as a caching key
+ -- to avoid buildId collissions
+ self.buildList[common.sha1(source.name .. "-" .. build.buildId)] = buildInfoToCache(build, source)
+ end
+end
+
+--- Version 1 of the API Contract for Builds
+---@param source APICapabilities
+---@param params BuildVersion1Filter
+function APIContractBuilds:GetBuildsVersion1(source, params)
+ if source.endpointType == EndpointType.CSV then
+ -- TODO: Implement CSV Parsing and Format
+ return
+ end
+
+ launch:DownloadPage(source.baseAPIPath .. "/v1/builds" .. tableToQueryParams(params), function(...)
+ self:BuildsVersion1Callback(source, ...)
+ end, {})
+end