> ## Documentation Index
> Fetch the complete documentation index at: https://docs.consile.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# API reference: Ordrestyring

> The 9 read-only tools in the Ordrestyring connector: cases, per-case hour registrations, material cost and sales invoices, and a read-only GraphQL escape hatch with schema discovery. Covers cursor paging, Copenhagen date ranges, row caps, the amount-unit caveat and the error contract.

All Ordrestyring tools are **read-only** and are called via `tools/call` over
`POST /ordrestyring/mcp`. The connector is connected with an **API key** that
Ordrestyring's support issues per agreement. There is no OAuth and there are no
scopes. See [Forbind Ordrestyring](/connect/forbind-ordrestyring).

<Info>
  Endpoint: `https://mcp.consile.ai/ordrestyring/mcp`. Ordrestyring is its own named
  connector with only its own tools. The transport namespaces each tool as
  `ordrestyring__<tool>` (e.g. `ordrestyring__list_cases`). The headings below use
  the bare tool names; in a `tools/call` request always send the namespaced name,
  as in the examples. A bare name returns `Unknown tool.`

  The connector is **multi-instance**: each additional Ordrestyring agreement the
  customer adds as a portal is served on its own endpoint, e.g.
  `https://mcp.consile.ai/ordrestyring-2/mcp`, with tools named
  `ordrestyring-2__<tool>`. Each portal uses only its own agreement's key.

  Besides these 9 tools, `tools/list` on every Ordrestyring endpoint also returns
  one unprefixed system tool, `connect_account` (no arguments). It returns
  `{ connect_url, instructions }`: a link, valid for 10 minutes, that opens the
  Consile portal's connect form for that endpoint, where the API key is pasted. It
  does not read any Ordrestyring data.
</Info>

<Note>
  **Read-only by construction.** The Ordrestyring API key grants full access
  upstream, so the connector checks every GraphQL document before it is sent. This
  includes the documents behind the fixed tools. A document is accepted only if it
  parses, contains exactly one operation, and that operation is a `query`
  (fragment definitions are allowed). The following are refused before any request
  is made: mutations, subscriptions, several operations in one document, schema
  definitions, unparseable documents, and documents longer than 65,536 characters
  or with more than 20,000 tokens. Every tool is annotated `readOnlyHint: true`,
  `destructiveHint: false`, `openWorldHint: false`. There are no confirm-gated
  tools.
</Note>

## Shared conventions

### One key, one company

One API key belongs to exactly one Ordrestyring agreement (one company). No tool
takes a company or agreement parameter, because the key scopes every request. A
customer with several agreements connects one portal (one key) per agreement.

### Case id vs. case number

`get_case`, `get_case_work_summary`, `list_case_hours` and
`list_case_sales_invoices` take the numeric Ordrestyring case **id**, not the
human-facing `caseNumber`. Resolve a case number to its id with `find_cases`, or
read `id` from `list_cases`.

<ParamField path="id / caseId" type="integer" required>
  Positive integer: the Ordrestyring case id (not the `caseNumber`).
</ParamField>

### Date ranges

The hour and invoice tools filter by calendar day in **Europe/Copenhagen** local time.

<ParamField path="from" type="string (YYYY-MM-DD)">
  First day, inclusive from local midnight. Defaults to `2000-01-01`.
</ParamField>

<ParamField path="to" type="string (YYYY-MM-DD)">
  Last day, inclusive as a whole day. The bound sent upstream is the following local
  midnight, exclusive. Defaults to today in Copenhagen.
</ParamField>

A `from` after `to` is rejected. Every date-filtered result echoes the window it used:

```json theme={null}
"period": {
  "from": "2026-08-01",
  "to": "2026-08-31",
  "fromUnix": 1785535200,
  "toUnix": 1788213600,
  "toIsExclusive": true,
  "timeZone": "Europe/Copenhagen"
}
```

### Row caps and truncation

* `list_cases` returns one page per call. `limit` is 1-200 (default 50).
* `list_case_hours` and `list_case_sales_invoices` follow pages of 200 internally
  and accumulate up to `maxRows` (default 500, max 2,000).
* `get_case_work_summary` reads up to 2,000 hour rows and 2,000 invoices.
* `truncated: true` means rows were left behind.

### Amounts

Ordrestyring does not document the unit of its amounts (its legacy REST API used
øre). Every amount is passed through exactly as Ordrestyring returns it, and every
money-bearing result carries an `amountsNote` saying so. Check one known case
against the Ordrestyring UI before treating a number as DKK.

### The case object

Returned by `list_cases`, `find_cases` and `get_case`:

| Field | Meaning |
| - | - |
| `id` | Numeric case id (use it with the per-case tools) |
| `caseNumber` | Case number as shown in Ordrestyring |
| `description` | Case description |
| `status.text` | Status |
| `caseType.text` | Case type |
| `customer.name` | Customer name |
| `foreman.fullName` | Responsible foreman |
| `offerTotal` | Offer total |
| `parentCaseId` | Parent case id, when the case is a sub-case |
| `subCases[].id` | Ids of the sub-cases |
| `economy.expectedCoverageRatio` | Expected coverage ratio |
| `economy.revenue` | Revenue |
| `economy.hoursSalesprice` / `economy.hoursCostprice` | Hours at sales / cost price |
| `economy.materialsSalesprice` / `economy.materialsCostprice` | Materials at sales / cost price |

***

## Cases

### list\_cases

List cases, one page per call, with the case object above. Pass the returned
`nextCursor` back as `cursor`. `hasMore: false` means every case has been seen.
<ParamField path="cursor" type="string">Non-empty cursor from a previous call's `nextCursor`. Omit for the first page.</ParamField>
<ParamField path="limit" type="integer" default="50">Rows per page, 1-200.</ParamField>
<ParamField path="orderBy" type="enum" default="updatedAt">`updatedAt` or `id`.</ParamField>
<ParamField path="direction" type="enum" default="DESC">`ASC` or `DESC` (`DESC` = newest first).</ParamField>

Returns `{ count, items, nextCursor, hasMore }`.

### find\_cases

Case-insensitive substring match on `caseNumber`, `description` and `customer.name`.
Ordrestyring has no confirmed server-side case search, so the tool scans the most
recently updated cases (sorted `updatedAt DESC`, pages of 200) and filters them in
the connector. A case updated before the scanned window is not found.
<ParamField path="text" type="string" required>Substring to match (case-insensitive).</ParamField>
<ParamField path="maxPages" type="integer" default="3">Pages of 200 recently updated cases to scan, 1-5 (600 cases by default, 1,000 at most).</ParamField>

Returns `{ count, items, scanned, pagesScanned, truncated, note? }`. `truncated: true`
means older cases exist that were not scanned, and `note` says so.

### get\_case

One case by its numeric id, with the case object above.
<ParamField path="id" type="integer" required>Case id (not the case number).</ParamField>

Returns the case object, or `null`. An id that does not exist comes back as an
Ordrestyring validation error naming the field (see [Errors](#errors-and-timeouts)).

***

## Economy per case

### get\_case\_work\_summary

One combined picture of what has been spent on and invoiced from a case. Hour
registrations are filtered on their start time and sales invoices on their date,
both by the date range. The material total is an aggregate for the **whole case**
and is not date-filtered. The hours section reports how many hour registrations
exist and their total cost price. It does not report the number of hours worked or
a sales price.
<ParamField path="caseId" type="integer" required>Case id.</ParamField>

<ParamField path="from" type="string (YYYY-MM-DD)" />

<ParamField path="to" type="string (YYYY-MM-DD)" />

Example result (illustrative values):

```json theme={null}
{
  "caseId": 18342,
  "period": { "from": "2026-08-01", "to": "2026-08-31", "fromUnix": 1785535200, "toUnix": 1788213600, "toIsExclusive": true, "timeZone": "Europe/Copenhagen" },
  "hours": { "rows": 42, "costPriceTotal": 1386000, "truncated": false },
  "materials": { "totalCostPrice": 845000, "scope": "whole case (not date-filtered)" },
  "salesInvoices": { "count": 2, "amountTotal": 3125000, "invoiceIds": [5531, 5560], "truncated": false },
  "amountsNote": "Amounts are passed through exactly as Ordrestyring returns them. Ordrestyring does not document the unit (their legacy REST API used øre), so check one known case against the Ordrestyring UI before treating a number as DKK."
}
```

The three sections are read independently. If one section fails, it comes back as
`{ "unavailable": "<ErrorName>: <message>" }` (message cut at 200 characters) and
the other sections still answer. Two cases fail the whole call instead: a rejected
key or a rate limit in any section, and all three sections failing.

### list\_case\_hours

The hour registrations on a case in a date range, filtered on the registration's
start time. Each row contains only `costPrice`, which is the only verified field.
For employee, hour type, duration or sales price, use `describe_type` and
`graphql_query`.
<ParamField path="caseId" type="integer" required>Case id.</ParamField>

<ParamField path="from" type="string (YYYY-MM-DD)" />

<ParamField path="to" type="string (YYYY-MM-DD)" />

<ParamField path="maxRows" type="integer" default="500">Rows to accumulate across pages, 1-2,000.</ParamField>

Returns `{ caseId, period, count, costPriceTotal, items: [{ costPrice }], truncated, amountsNote }`.

### list\_case\_sales\_invoices

The sales invoices raised on a case in a date range, filtered on the invoice date.
Each row contains `id` and `amount`.
<ParamField path="caseId" type="integer" required>Case id.</ParamField>

<ParamField path="from" type="string (YYYY-MM-DD)" />

<ParamField path="to" type="string (YYYY-MM-DD)" />

<ParamField path="maxRows" type="integer" default="500">Rows to accumulate across pages, 1-2,000.</ParamField>

Returns `{ caseId, period, count, amountTotal, items: [{ id, amount }], truncated, amountsNote }`.

***

## Schema discovery and free queries

The dedicated tools cover the fields verified against the live API. Everything else
in Ordrestyring's GraphQL schema is reachable through these three tools. Use them in
this order: `introspect_schema`, then `describe_type`, then `graphql_query`.

### introspect\_schema

Lists the top-level query fields with their arguments and result types as one-line
signatures (`field(arg: Type): Result`). It never returns the whole schema.
<ParamField path="search" type="string">Case-insensitive filter on field name and description.</ParamField>
<ParamField path="limit" type="integer" default="25">Maximum query fields to return, 1-100.</ParamField>
<ParamField path="includeTypes" type="boolean" default="false">Also return matching type names (max 100) to pass to `describe_type`.</ParamField>

Returns `{ queryTypeName, total, returned, truncated, fields: [{ name, description, signature }], typeNames?, typeNamesTruncated? }`.
Results over 40,000 characters are refused. If introspection is disabled for the
account, the call fails. In that case, use the conventions listed under
`graphql_query`.

### describe\_type

Describes one GraphQL type: kind, output fields (with argument and result types),
input fields (for filter and pagination inputs) and enum values.
<ParamField path="name" type="string" required>Exact type name, case-sensitive (e.g. `Case`).</ParamField>
<ParamField path="limit" type="integer" default="60">Maximum entries per section, 1-200.</ParamField>

Returns `{ name, kind, description, found: true, fields, inputFields, enumValues, interfaces?, possibleTypes? }`.
Each section has the form `{ total, returned, truncated, items }`. An unknown name
returns `{ name, found: false }`. Results over 40,000 characters are refused.

### graphql\_query

Runs an arbitrary read-only GraphQL query and returns the `data` object unchanged.
<ParamField path="query" type="string" required>A GraphQL document with exactly one `query` operation (fragments allowed). Never a mutation or subscription.</ParamField>
<ParamField path="variables" type="object">Optional variables for the document.</ParamField>

Conventions:

* Collections take `pagination: { cursor, limit }` (limit max 200) and return `{ items, nextCursor }`.
* `orderBy: { field: "updatedAt", direction: DESC }`: `field` is a quoted string and `direction` an enum.
* Some collections take `between: { field: startTime, from: <unix seconds>, to: <unix seconds> }`: here `field` is an unquoted enum.

```json theme={null}
{
  "query": "query($cursor: String) { cases(pagination: { cursor: $cursor, limit: 20 }, orderBy: { field: \"updatedAt\", direction: DESC }) { items { id caseNumber status { text } } nextCursor } }",
  "variables": { "cursor": null }
}
```

Results over 100,000 characters (serialized JSON) are refused: narrow the selection
set or lower `limit`. A response that contains any GraphQL error fails the call,
even if partial `data` came back.

***

## Errors and timeouts

Tool failures come back as a normal tool result with `isError: true` and one text
block. Ordrestyring is one of the connectors that returns explanatory messages: the
first three rows below are the connector's own, the next two come from the
platform, and everything else returns the generic text.

| Situation | What the tool returns |
| - | - |
| Ordrestyring rejects the key (HTTP 401/403, or an `AuthenticationException` inside an HTTP 200 response) | `Ordrestyring rejected the API key (Unauthenticated). The connection must be re-established from the portal with a valid API key.` |
| Still throttled after one retry (HTTP 429 or a `ThrottleRequestsException`; the retry waits for `Retry-After`, at most 5 s) | `Ordrestyring rate limit hit; retried once and still limited. Wait a moment and try again.` |
| Ordrestyring `ValidationError`, e.g. an unknown case id | `Ordrestyring rejected the request: <field>: <messages>` |
| No key saved for this portal yet | `ordrestyring is not connected yet. Connect it here: <link>` (link valid 10 minutes). An additional portal names itself, e.g. `ordrestyring-2 is not connected yet. …`. The link opens the portal's connect form. |
| The user has no seat on Ordrestyring | `Your company subscribes to ordrestyring through Consile, but your user does not hold a seat on it yet. …` (an administrator assigns seats under Team) |
| Anything else: arguments outside the input schema (e.g. `limit` above 200, a date not in `YYYY-MM-DD`, an `id` that is not a positive integer), a refused write document, an oversized result, `from` after `to`, other GraphQL errors, upstream HTTP errors, or a timeout | `Tool invocation failed.` |

Each upstream request times out after 20 s. Only throttling is retried, once. See
[Errors & limits](/developers/errors-and-limits) for the protocol-level errors,
the other platform refusals and the rate limits shared by all connectors.

***

## Code examples

<CodeGroup>
  ```json Request: find_cases theme={null}
  {
    "name": "ordrestyring__find_cases",
    "arguments": { "text": "Jensen VVS" }
  }
  ```

  ```json Request: get_case_work_summary theme={null}
  {
    "name": "ordrestyring__get_case_work_summary",
    "arguments": { "caseId": 18342, "from": "2026-08-01", "to": "2026-08-31" }
  }
  ```

  ```json Request: list_cases (next page) theme={null}
  {
    "name": "ordrestyring__list_cases",
    "arguments": { "cursor": "<nextCursor from the previous call>", "limit": 100 }
  }
  ```

  ```json Request: describe_type theme={null}
  {
    "name": "ordrestyring__describe_type",
    "arguments": { "name": "Case", "limit": 100 }
  }
  ```
</CodeGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.