Files
Fuchs_Intranet/MFR_RESTClient/Docs/mfr_interface_description.md
Stefan 27becf7c68 MFR client: align with interface doc; remove VB-era files
Per MFR_RESTClient/Docs/mfr_interface_description.md (added):
- MFRClientConfig: configurable TimeoutMs (default 30000), UserAgent, MaxRetries,
  and a derived RestRoot (/mfr) alongside the OData BaseUrl.
- MFRClient: apply request Timeout + UserAgent, send Accept: application/json,
  and retry idempotent GETs on transient failures (HTTP 429/5xx and
  network/timeout) with exponential backoff + jitter, honouring Retry-After.
- Added ReadODataAllPages to follow @odata.nextLink pagination.

Cleanup: removed legacy VB project files (MFR_RESTClient.vbproj, .vbproj.user,
app.config My.MySettings) and stopped tracking the generated MFR_RESTClient.xml
(now git-ignored). The active project is MFR_RESTClient.csproj.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 15:04:19 +02:00

1277 lines
40 KiB
Markdown

---
title: "mfr Mobile Field Report - Interface Description for AI Coding Tools"
subtitle: "REST/OData integration contract derived from the public mfr wiki, public Postman documentation context, and public implementation evidence"
author: "Prepared for AI-assisted development"
date: "2026-06-05"
---
# mfr Mobile Field Report - Interface Description for AI Coding Tools
## 1. Purpose and scope
This document describes the public integration surface of **mfr - mobile field report** in a form that can be handed to AI coding tools, SDK generators, or developers building integrations.
The intent is not to replace the vendor's current Postman collection or tenant-specific API documentation. Instead, it provides a structured, implementation-oriented contract with:
- API surfaces and base URLs.
- Authentication assumptions.
- OData conventions.
- Domain resources and likely entity relationships.
- Endpoint catalog with confidence labels.
- Request/response examples.
- Error-handling, pagination, filtering, and retry guidance.
- TypeScript SDK design guidance.
- An OpenAPI starter definition for the confirmed REST endpoints and common OData endpoints.
- Verification checklist for turning this draft into a production-certified contract.
## 2. Source map and confidence model
### 2.1 Source URLs supplied
1. Official mfr wiki page: `https://faq.mobilefieldreport.com/de/wiki/beschreibung-schnittstelle`
2. Public Postman documentation URL: `https://documenter.getpostman.com/view/6932380/2sB3dWsn6U`
### 2.2 Additional public evidence used
The following public evidence was used to fill in implementation details where the vendor wiki only describes the high-level interface family:
- Public Postman/OData indexing snippets for older mfr OData documentation.
- Public n8n community discussion and public node implementation references for mfr document upload.
- Public node implementation evidence for OData endpoints such as Companies, Contacts, Appointments, ItemTypes, and ServiceObjects.
- OData standard documentation for query semantics.
- Ecosystem references such as Zapier connector fields and public integration guides.
### 2.3 Confidence labels
Use the following labels whenever implementing or generating code from this document.
| Label | Meaning | How to use it |
|---|---|---|
| Confirmed | Directly visible in official mfr wiki or strongly supported by official-looking endpoint examples. | Safe to implement first, still validate against tenant credentials. |
| Strongly indicated | Supported by public implementation evidence or older public Postman/OData documentation. | Implement behind integration tests; verify against current Postman collection. |
| Inferred | Derived from OData standards or naming conventions. | Treat as a default strategy, not a contract guarantee. |
| Unknown / verify | Not available from public text extraction or likely tenant-specific. | Do not hard-code; ask tenant/vendor or inspect live metadata. |
## 3. Interface families
The official wiki identifies these integration mechanisms:
| Interface | Description | Typical use |
|---|---|---|
| UGL Artikel Import | Import of item/article data. | Bulk item/product import, likely from ERP/catalog systems. |
| OData | Microsoft-origin REST data-exchange standard; successor-style integration surface compared with older SOAP-style integrations. | CRUD and querying over business entities. |
| REST API | HTTP operations via POST, GET, PUT, and DELETE for data and documents. | Special operations such as deep creation and document upload. |
| Navision / Microsoft Dynamics development kit | Integration kit for Dynamics/Navision ecosystems. | ERP connection and synchronization. |
| AMQP Message Bus | Change notifications. | Event-driven synchronization, cache invalidation, async workflows. |
The rest of this document focuses on the HTTP APIs: **OData** and **REST API**.
## 4. Base URLs and environments
### 4.1 Observed base host
The public examples consistently use:
```text
https://portal.mobilefieldreport.com
```
### 4.2 Resource roots
| Root | Confidence | Meaning |
|---|---:|---|
| `https://portal.mobilefieldreport.com/odata` | Strongly indicated | OData resource root for entity CRUD and queries. |
| `https://portal.mobilefieldreport.com/mfr` | Confirmed | REST operation root for mfr-specific operations. |
### 4.3 Recommended SDK configuration
Do not hard-code the host. Provide configuration:
```ts
export interface MfrClientConfig {
baseUrl?: string; // default: https://portal.mobilefieldreport.com
odataPath?: string; // default: /odata
restPath?: string; // default: /mfr
username: string;
password: string;
timeoutMs?: number; // default: 30000
userAgent?: string;
}
```
## 5. Authentication and headers
### 5.1 Authentication
Public implementation evidence indicates **HTTP Basic Authentication** using a username and password. Confirm against the tenant's current Postman collection before production release.
Recommended client behavior:
- Use HTTPS only.
- Store credentials in a secrets manager or environment variables.
- Never log the raw `Authorization` header.
- Support tenant-specific credentials.
- Fail fast if credentials are missing.
Example environment variables:
```bash
MFR_BASE_URL="https://portal.mobilefieldreport.com"
MFR_USERNAME="service-account@example.com"
MFR_PASSWORD="***"
```
### 5.2 Default headers
For JSON operations:
```http
Accept: application/json
Content-Type: application/json
Authorization: Basic <base64(username:password)>
```
For file upload:
```http
Accept: application/json
Content-Type: multipart/form-data; boundary=<generated>
Authorization: Basic <base64(username:password)>
```
Let the HTTP client generate the `multipart/form-data` boundary.
## 6. OData conventions
### 6.1 Entity paths and IDs
Observed OData entity URLs use the format:
```text
/odata/ServiceObjects(9875849220L)
/odata/Companies(1234567890L)
```
Implementation guidance:
- Treat entity IDs as **64-bit numeric identifiers**.
- Use `string` or `bigint` in JavaScript/TypeScript to avoid precision loss.
- Public examples append an `L` suffix to path IDs. Preserve this suffix when constructing OData entity URLs unless live metadata proves otherwise.
- Some list operations support filtering by `ExternalId`.
Recommended TypeScript ID type:
```ts
export type MfrId = string; // store as decimal string; append L only in URL path builder
```
### 6.2 OData query parameters
Use standard OData query options where supported:
| Option | Use |
|---|---|
| `$filter` | Server-side filtering. |
| `$select` | Reduce response fields. |
| `$expand` | Load related entities, e.g. `Contacts`. |
| `$orderby` | Sort results. |
| `$top` | Limit page size. |
| `$skip` | Offset pagination. |
| `$count` | Ask for total count if the service supports it. |
### 6.3 Filtering examples
```text
/odata/Companies?$filter=ExternalId eq 'CUST-10001'
/odata/ServiceObjects?$filter=ExternalId eq 'SO-20002'
/odata/Appointments?$filter=StartDateTime ge datetime'2026-06-01T00:00:00Z'
/odata/Contacts?$filter=CompanyId eq 1234567890L&$expand=Company
```
Implementation guidance:
- URL-encode query parameters.
- Escape single quotes in OData string literals by doubling them: `O'Brien` -> `'O''Brien'`.
- Treat datetime literal syntax as implementation-specific until verified. Public integration guidance indicates `datetime'YYYY-MM-DDTHH:mm:ssZ'`.
- Prefer ISO 8601 UTC timestamps in generated code.
### 6.4 Pagination
The public OData implementation evidence uses `$top` and `$skip`.
Recommended default pagination strategy:
```ts
const pageSize = 100;
for (let skip = 0; ; skip += pageSize) {
const page = await client.listCompanies({ top: pageSize, skip });
if (page.length === 0) break;
yield* page;
if (page.length < pageSize) break;
}
```
Avoid assuming server-side continuation tokens unless discovered in live responses.
### 6.5 Updating entities
For OData updates, verify the supported method in the current tenant. Common OData patterns are:
- `POST /odata/EntitySet` to create.
- `GET /odata/EntitySet(idL)` to read.
- `PUT /odata/EntitySet(idL)` or `PATCH /odata/EntitySet(idL)` to update.
- `DELETE /odata/EntitySet(idL)` to delete.
If the Postman collection exposes only some verbs, implement only the exposed verbs.
## 7. Domain model overview
The core mfr domain is field-service execution: service requests/jobs, service objects/locations, customers/companies, appointments, technicians/users, contacts, item types/products, documents, checklists, reports, and billing/time data.
### 7.1 Core entities
| Entity | Likely OData set | Description | Confidence |
|---|---|---|---:|
| Company / Customer | `Companies` | Customer organization or physical person. | Strongly indicated |
| Contact | `Contacts` | Contact person with telephone, mobile, and email. | Strongly indicated |
| Service Request / Job | `ServiceRequests` | Work order/job/task container. | Confirmed / strongly indicated |
| Service Object / Location | `ServiceObjects` | Installed object, location, asset, or service site. | Confirmed / strongly indicated |
| Appointment | `Appointments` | Scheduled visit/time slot, optionally linked to request, service object, contact, technician. | Strongly indicated |
| Item Type / Product | `ItemTypes` | Product, article, service item, stock/material type. | Strongly indicated |
| Document | REST document operations | File upload and association. | Strongly indicated |
| User / Technician | `Users` or equivalent | Technician / mfr user account. | Strongly indicated |
| Tags | unknown | Labels attached to jobs/entities. | Strongly indicated by ecosystem connectors |
| Reports | REST report operation | Generate report by job and report definition code. | Strongly indicated by ecosystem connectors |
### 7.2 Customer / Company model
Observed and inferred fields:
```ts
export interface MfrCompany {
Id?: MfrId;
Name: string;
ExternalId?: string;
IsPhysicalPerson?: boolean | 0 | 1;
Location?: MfrLocation;
MainContact?: MfrContact;
SupportTelephone?: string;
SupportFax?: string;
SupportMail?: string;
Note?: string;
}
```
### 7.3 Location model
```ts
export interface MfrLocation {
Id?: MfrId;
AddressString?: string;
Postal?: string;
City?: string;
Country?: string; // e.g. DE
Latitude?: number;
Longitude?: number;
}
```
### 7.4 Contact model
```ts
export interface MfrContact {
Id?: MfrId;
FirstName?: string;
LastName?: string;
Telephone?: string;
MobilePhone?: string;
Email?: string;
CompanyId?: MfrId;
}
```
### 7.5 Service object model
```ts
export interface MfrServiceObject {
Id?: MfrId;
Name: string;
ExternalId?: string;
CompanyId?: MfrId;
Location?: MfrLocation;
Contacts?: MfrContact[];
Country?: string;
CreateGeoLocation?: boolean;
CreateFromServiceRequestTemplateId?: MfrId;
}
```
### 7.6 Service request / job model
```ts
export type ServiceRequestState =
| 'ReadyForScheduling'
| 'Draft'
| 'Scheduled'
| 'InProgress'
| 'Completed'
| 'Cancelled'
| string; // keep extensible because tenant values may vary
export interface MfrServiceRequest {
Id?: MfrId;
Name: string;
Description?: string;
ExternalId?: string;
CustomerId?: MfrId;
Customer?: MfrCompany;
State?: ServiceRequestState;
ServiceObjects?: MfrServiceObject[];
Appointments?: MfrAppointment[];
}
```
### 7.7 Appointment model
```ts
export interface MfrAppointment {
Id?: MfrId;
ContactId?: MfrId;
ServiceRequestId?: MfrId;
ServiceObjectId?: MfrId;
StartDateTime: string; // ISO 8601
EndDateTime: string; // ISO 8601
Type?: string;
Description?: string;
TechnicianUsername?: string;
}
```
### 7.8 Item type / product model
```ts
export interface MfrItemType {
Id?: MfrId;
NameOrNumber: string;
ExternalId?: string;
UnitId?: MfrId;
Type?: string;
Costs?: number;
Price?: number;
Manufacture?: string;
VAT?: number;
Description?: string;
GlobalTradeItemNr?: string;
}
```
## 8. Confirmed REST operation: deep create service request
### 8.1 Endpoint
```http
POST /mfr/ServiceRequest/Deep
Host: portal.mobilefieldreport.com
Content-Type: application/json
Accept: application/json
```
Full observed URL:
```text
https://portal.mobilefieldreport.com/mfr/ServiceRequest/Deep
```
### 8.2 Purpose
Create a service request/job together with nested customer and service-object data in one operation.
### 8.3 Important field behavior
`CreateFromServiceRequestTemplateId` determines which order/service-request template is used when creating the order. The template ID is found in mfr administration under templates (`Verwaltung > Vorlagen`) according to the official wiki example.
### 8.4 Request body example
```json
{
"Name": "Auftragsbezeichnung",
"Description": "Auftragsbeschreibung",
"Customer": {
"Id": 0,
"IsPhysicalPerson": 1,
"ExternalId": "543",
"Name": "Frank Service GmbH",
"Location": {
"Postal": "23423",
"AddressString": "Dorfstrasse 3",
"City": "Leipzig"
}
},
"State": "ReadyForScheduling",
"ServiceObjects": [
{
"Id": 0,
"CreateFromServiceRequestTemplateId": 2342342,
"CreateGeoLocation": true,
"Country": "DE",
"Contacts": [
{
"FirstName": "Frank",
"LastName": "Peterson",
"Telephone": "023423",
"MobilePhone": "234234",
"Email": "test@test.de"
}
],
"Name": "Service object name",
"ExternalId": "28",
"Location": {
"AddressString": "Gleisstrasse 2",
"Postal": "04229",
"City": "Leipzig"
}
}
]
}
```
### 8.5 Response body
The exact response shape must be verified from the current Postman collection or live tenant. A robust client should support these common response patterns:
```ts
export type DeepCreateServiceRequestResponse =
| MfrServiceRequest
| { Id: MfrId; ServiceRequestId?: MfrId; [key: string]: unknown }
| { value: MfrServiceRequest };
```
### 8.6 Validation rules for generated clients
- Require `Name`.
- Require either a nested `Customer` or a known `CustomerId` if tenant supports it.
- Require at least one service object when using the nested official example flow.
- Validate email format where provided, but do not reject missing contact fields unless the tenant requires them.
- Use integer/decimal-string template IDs, not floating point.
- Support German and international address formats.
## 9. Strongly indicated REST operation: document upload and create
### 9.1 Endpoint
```http
POST /mfr/Document/UploadAndCreate
Host: portal.mobilefieldreport.com
Content-Type: multipart/form-data
Accept: application/json
```
Full observed URL:
```text
https://portal.mobilefieldreport.com/mfr/Document/UploadAndCreate
```
### 9.2 Purpose
Upload a document file and create an mfr document record, usually to associate the document with a job, service request, service object, or another entity.
### 9.3 Multipart fields
Public implementation evidence shows a multipart request containing:
| Field | Type | Required | Notes |
|---|---|---:|---|
| `file` | binary file | yes | The file content, with filename and content type. |
| `options` | JSON string | likely yes | Public implementation evidence includes `{ "filename": "..." }`. Tenant/Postman may define additional association fields. |
### 9.4 Example multipart pseudo-code
```ts
const form = new FormData();
form.append('file', fileBuffer, {
filename: 'report.pdf',
contentType: 'application/pdf'
});
form.append('options', JSON.stringify({
filename: 'report.pdf'
// Verify exact association fields in current Postman docs:
// serviceRequestId, jobId, entityId, entityType, description, etc.
}));
await http.post('/mfr/Document/UploadAndCreate', form, {
headers: form.getHeaders()
});
```
### 9.5 Client design guidance
Because the public text does not expose the complete `options` schema, generate an extensible type:
```ts
export interface UploadDocumentOptions {
filename?: string;
serviceRequestId?: MfrId;
jobId?: MfrId;
serviceObjectId?: MfrId;
entityId?: MfrId;
entityType?: string;
description?: string;
[key: string]: unknown;
}
```
Then expose:
```ts
uploadDocument(file: Blob | Buffer | Readable, options: UploadDocumentOptions): Promise<MfrDocument>
```
## 10. OData endpoint catalog
This catalog lists endpoints that are confirmed or strongly indicated by public evidence. Verify all mutable operations against the current Postman collection before production use.
### 10.1 Companies
**Entity set:** `Companies`
**Purpose:** customer/company master data.
```http
GET /odata/Companies?$top=100&$skip=0&$filter=ExternalId eq 'CUST-001'
GET /odata/Companies({id}L)
POST /odata/Companies
PUT/PATCH/DELETE /odata/Companies({id}L) # verify
```
Common create/update body:
```json
{
"Name": "Example GmbH",
"ExternalId": "CUST-001",
"IsPhysicalPerson": false,
"Location": {
"AddressString": "Hauptstrasse 1",
"Postal": "10115",
"City": "Berlin",
"Country": "DE"
},
"MainContact": {
"FirstName": "Erika",
"LastName": "Mustermann",
"Email": "erika@example.com",
"Telephone": "+49 30 123456"
},
"SupportTelephone": "+49 30 55555",
"SupportMail": "support@example.com",
"Note": "Imported from ERP"
}
```
### 10.2 Contacts
**Entity set:** `Contacts`
**Purpose:** people/contact data.
```http
GET /odata/Contacts?$top=100&$skip=0&$expand=Company
GET /odata/Contacts({id}L)
POST /odata/Contacts # verify if exposed in current collection
PUT/PATCH/DELETE /odata/Contacts({id}L) # verify
```
Common fields:
```json
{
"FirstName": "Frank",
"LastName": "Peterson",
"Telephone": "023423",
"MobilePhone": "234234",
"Email": "frank.peterson@example.com",
"CompanyId": "1234567890"
}
```
### 10.3 Service objects
**Entity set:** `ServiceObjects`
**Purpose:** service sites, assets, or locations connected to customers and jobs.
```http
GET /odata/ServiceObjects?$top=100&$skip=0&$expand=Contacts
GET /odata/ServiceObjects({id}L)?$expand=Contacts
POST /odata/ServiceObjects
PUT/PATCH/DELETE /odata/ServiceObjects({id}L) # verify
```
Common create body:
```json
{
"Name": "Boiler Room A",
"ExternalId": "SO-001",
"CompanyId": "1234567890",
"Location": {
"AddressString": "Gleisstrasse 2",
"Postal": "04229",
"City": "Leipzig",
"Country": "DE"
}
}
```
### 10.4 Service requests / jobs
**Likely entity set:** `ServiceRequests`
**Purpose:** job/work-order/task data.
```http
GET /odata/ServiceRequests?$top=100&$skip=0&$filter=ExternalId eq 'JOB-001'
GET /odata/ServiceRequests({id}L)
POST /mfr/ServiceRequest/Deep
POST /odata/ServiceRequests # verify if simple create is exposed
PUT/PATCH/DELETE /odata/ServiceRequests({id}L) # verify
```
Use the deep REST endpoint when nested creation is required.
### 10.5 Appointments
**Entity set:** `Appointments`
**Purpose:** scheduled service visits.
```http
GET /odata/Appointments?$top=100&$skip=0
GET /odata/Appointments({id}L)
POST /odata/Appointments
PUT/PATCH/DELETE /odata/Appointments({id}L) # verify
```
Common create body:
```json
{
"ContactId": "1234567890",
"StartDateTime": "2026-06-10T08:00:00Z",
"EndDateTime": "2026-06-10T10:00:00Z",
"Type": "Service"
}
```
Ecosystem connector evidence indicates appointment creation may also require appointment type, duration, start date/time, end date/time, description, and technician username in some flows. Treat technician assignment fields as tenant-specific until verified.
### 10.6 Item types / products
**Entity set:** `ItemTypes`
**Purpose:** product/article/service item master data.
```http
GET /odata/ItemTypes?$top=100&$skip=0&$filter=ExternalId eq 'ITEM-001'
GET /odata/ItemTypes({id}L)
POST /odata/ItemTypes
PUT/PATCH/DELETE /odata/ItemTypes({id}L) # verify
```
Common create body:
```json
{
"NameOrNumber": "PUMP-001",
"ExternalId": "ERP-ITEM-001",
"UnitId": "1",
"Type": "Material",
"Costs": 12.50,
"Price": 19.95,
"Manufacture": "Example Manufacturer",
"VAT": 19,
"Description": "Replacement pump",
"GlobalTradeItemNr": "4000000000000"
}
```
### 10.7 Entity links / relationships
Older public Postman/OData snippets show relationship operations like:
```http
POST /odata/ServiceObjects({serviceObjectId}L)/$links/Contacts
```
The exact request body for link creation must be verified. Common OData link bodies use a URI reference pattern such as:
```json
{
"uri": "https://portal.mobilefieldreport.com/odata/Contacts(1234567890L)"
}
```
Do not implement link writes without validating the body shape against live Postman documentation.
## 11. Common workflows
### 11.1 Create a new customer and service request in one call
Preferred when using the official deep-create flow:
1. Build nested customer object.
2. Build at least one service object with location and contact.
3. Add `CreateFromServiceRequestTemplateId` if a template should be applied.
4. Set `State` to an accepted state such as `ReadyForScheduling`.
5. `POST /mfr/ServiceRequest/Deep`.
6. Store returned mfr IDs and external IDs in the source system.
### 11.2 Synchronize companies from ERP
1. For each ERP customer, query by `ExternalId`:
`GET /odata/Companies?$filter=ExternalId eq 'ERP-123'`.
2. If found, update the existing company if update is supported.
3. If not found, create via `POST /odata/Companies`.
4. Store the returned mfr `Id` as a cross-reference.
5. Use rate limiting and retry on transient 5xx/429 errors.
### 11.3 Create service object under an existing customer
1. Resolve customer/company by external ID.
2. `POST /odata/ServiceObjects` with `CompanyId` and `Location`.
3. Optionally attach contacts through nested data or OData link operations if supported.
4. Query with `$expand=Contacts` to verify relationships.
### 11.4 Create appointment for an existing job
1. Resolve service request/job ID.
2. Resolve technician/user if required by tenant.
3. Resolve appointment type if tenant requires enumerated types.
4. `POST /odata/Appointments` with start/end time and links.
5. Read back the appointment and verify assigned technician/status.
### 11.5 Upload document for a job
1. Read file from source system.
2. Build multipart form with `file` and `options`.
3. Include `filename` and association fields required by tenant/Postman collection.
4. `POST /mfr/Document/UploadAndCreate`.
5. Store returned document ID.
6. Read job/document relationship if an endpoint is available.
## 12. Error handling and retries
### 12.1 Expected HTTP status classes
| Status | Meaning | Client behavior |
|---:|---|---|
| 200 | Success, often for reads or operations returning an object. | Parse JSON. |
| 201 | Created. | Parse JSON and capture created ID. |
| 204 | Success without body. | Return void/success. |
| 400 | Invalid payload or query. | Do not retry; surface validation details. |
| 401 | Missing/invalid credentials. | Do not retry blindly; refresh/reconfigure credentials. |
| 403 | Not authorized for tenant/resource. | Do not retry; escalate permissions. |
| 404 | Resource not found. | Return typed not-found error. |
| 409 | Conflict/duplicate/concurrent update. | Consider idempotency lookup by `ExternalId`. |
| 413 | Upload too large. | Surface file size limit. |
| 415 | Unsupported media type. | Check `Content-Type` and file MIME type. |
| 429 | Rate limited. | Retry with backoff if allowed. |
| 500-599 | Server/transient errors. | Retry idempotent operations with backoff. |
### 12.2 Error parser
Generated clients should not assume a single error body shape. Implement a permissive parser:
```ts
export interface MfrApiErrorBody {
error?: unknown;
message?: string;
Message?: string;
details?: unknown;
[key: string]: unknown;
}
```
### 12.3 Retry policy
Recommended default:
- Retry only GET and explicitly idempotent operations by default.
- Retry 429 and 5xx.
- Use exponential backoff with jitter.
- Never retry file uploads automatically unless the caller opts in.
- Before retrying create operations, search by `ExternalId` to avoid duplicates.
## 13. Idempotency and external IDs
Many integration flows include `ExternalId`. Treat `ExternalId` as the source-system cross-reference.
Recommended create-or-update pattern:
```ts
async function upsertCompany(input: MfrCompany): Promise<MfrCompany> {
if (!input.ExternalId) throw new Error('ExternalId is required for idempotent upsert');
const existing = await client.findCompanyByExternalId(input.ExternalId);
if (existing) return client.updateCompany(existing.Id!, input);
return client.createCompany(input);
}
```
If update is not available or not verified, implement create-only plus duplicate detection.
## 14. Time zones and date fields
Use UTC ISO 8601 strings for API payloads unless tenant docs specify local time. Store the user's/local timezone separately if required for scheduling UX.
Recommended conversion:
```ts
const startUtc = zonedTimeToUtc('2026-06-10 08:00', 'Europe/Berlin').toISOString();
```
Payload example:
```json
{
"StartDateTime": "2026-06-10T06:00:00Z",
"EndDateTime": "2026-06-10T08:00:00Z"
}
```
## 15. Security and privacy requirements
Generated integrations should follow these requirements:
- Use HTTPS only.
- Keep API credentials out of code and logs.
- Redact `Authorization`, password fields, file contents, and personal data in logs.
- Minimize `$expand` and `$select` to only needed fields.
- Avoid exporting unnecessary personal data from contacts and appointments.
- Add an audit trail for create/update/delete operations.
- Use service accounts with least privilege when possible.
- Treat uploaded files as potentially sensitive.
## 16. TypeScript SDK blueprint
### 16.1 Client class
```ts
export class MfrClient {
constructor(private config: MfrClientConfig) {}
// REST operations
createServiceRequestDeep(input: DeepCreateServiceRequestRequest): Promise<DeepCreateServiceRequestResponse>;
uploadDocument(file: Uploadable, options: UploadDocumentOptions): Promise<MfrDocument>;
// Companies
listCompanies(query?: ODataQuery): Promise<MfrCompany[]>;
getCompany(id: MfrId): Promise<MfrCompany>;
findCompanyByExternalId(externalId: string): Promise<MfrCompany | null>;
createCompany(input: MfrCompany): Promise<MfrCompany>;
updateCompany(id: MfrId, input: Partial<MfrCompany>): Promise<MfrCompany>;
// Contacts
listContacts(query?: ODataQuery): Promise<MfrContact[]>;
getContact(id: MfrId): Promise<MfrContact>;
// Service objects
listServiceObjects(query?: ODataQuery): Promise<MfrServiceObject[]>;
getServiceObject(id: MfrId, expand?: string[]): Promise<MfrServiceObject>;
createServiceObject(input: MfrServiceObject): Promise<MfrServiceObject>;
// Appointments
listAppointments(query?: ODataQuery): Promise<MfrAppointment[]>;
getAppointment(id: MfrId): Promise<MfrAppointment>;
createAppointment(input: MfrAppointment): Promise<MfrAppointment>;
// Item types
listItemTypes(query?: ODataQuery): Promise<MfrItemType[]>;
getItemType(id: MfrId): Promise<MfrItemType>;
findItemTypeByExternalId(externalId: string): Promise<MfrItemType | null>;
createItemType(input: MfrItemType): Promise<MfrItemType>;
}
```
### 16.2 URL builders
```ts
function formatMfrId(id: MfrId): string {
const value = String(id).replace(/L$/, '');
if (!/^\d+$/.test(value)) throw new Error(`Invalid MFR numeric id: ${id}`);
return `${value}L`;
}
function entityUrl(entitySet: string, id: MfrId): string {
return `/odata/${entitySet}(${formatMfrId(id)})`;
}
```
### 16.3 OData query builder
```ts
export interface ODataQuery {
filter?: string;
select?: string[];
expand?: string[];
orderby?: string;
top?: number;
skip?: number;
count?: boolean;
}
function buildODataQuery(q: ODataQuery = {}): string {
const params = new URLSearchParams();
if (q.filter) params.set('$filter', q.filter);
if (q.select?.length) params.set('$select', q.select.join(','));
if (q.expand?.length) params.set('$expand', q.expand.join(','));
if (q.orderby) params.set('$orderby', q.orderby);
if (q.top != null) params.set('$top', String(q.top));
if (q.skip != null) params.set('$skip', String(q.skip));
if (q.count != null) params.set('$count', String(q.count));
const s = params.toString();
return s ? `?${s}` : '';
}
function odataStringLiteral(value: string): string {
return `'${value.replace(/'/g, "''")}'`;
}
```
### 16.4 HTTP implementation requirements
- Centralize HTTP transport.
- Add per-request timeout.
- Parse JSON only when `Content-Type` indicates JSON and body is non-empty.
- Include request ID/correlation ID in logs if server returns one.
- Throw typed errors with `status`, `method`, `url`, and redacted response body.
- Unit-test URL encoding and ID formatting.
## 17. OpenAPI starter definition
The following OpenAPI fragment is intentionally conservative. It models confirmed and strongly indicated routes but leaves unverified response schemas extensible.
```yaml
openapi: 3.1.0
info:
title: mfr Mobile Field Report Integration API
version: 0.1.0-draft
description: Draft integration contract. Verify against current mfr Postman collection and tenant metadata.
servers:
- url: https://portal.mobilefieldreport.com
security:
- basicAuth: []
components:
securitySchemes:
basicAuth:
type: http
scheme: basic
schemas:
MfrId:
type: string
pattern: '^\\d+$'
Location:
type: object
additionalProperties: true
properties:
AddressString: { type: string }
Postal: { type: string }
City: { type: string }
Country: { type: string }
Contact:
type: object
additionalProperties: true
properties:
Id: { $ref: '#/components/schemas/MfrId' }
FirstName: { type: string }
LastName: { type: string }
Telephone: { type: string }
MobilePhone: { type: string }
Email: { type: string, format: email }
Company:
type: object
additionalProperties: true
required: [Name]
properties:
Id: { $ref: '#/components/schemas/MfrId' }
Name: { type: string }
ExternalId: { type: string }
IsPhysicalPerson:
oneOf:
- type: boolean
- type: integer
enum: [0, 1]
Location: { $ref: '#/components/schemas/Location' }
MainContact: { $ref: '#/components/schemas/Contact' }
ServiceObject:
type: object
additionalProperties: true
required: [Name]
properties:
Id: { $ref: '#/components/schemas/MfrId' }
Name: { type: string }
ExternalId: { type: string }
CompanyId: { $ref: '#/components/schemas/MfrId' }
Location: { $ref: '#/components/schemas/Location' }
Contacts:
type: array
items: { $ref: '#/components/schemas/Contact' }
DeepCreateServiceRequestRequest:
type: object
additionalProperties: true
required: [Name]
properties:
Name: { type: string }
Description: { type: string }
State: { type: string }
Customer: { $ref: '#/components/schemas/Company' }
ServiceObjects:
type: array
items: { $ref: '#/components/schemas/ServiceObject' }
paths:
/mfr/ServiceRequest/Deep:
post:
operationId: createServiceRequestDeep
summary: Create a service request with nested customer and service objects
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DeepCreateServiceRequestRequest'
responses:
'200':
description: Created or returned service request
content:
application/json:
schema:
type: object
additionalProperties: true
'201':
description: Created
/mfr/Document/UploadAndCreate:
post:
operationId: uploadAndCreateDocument
summary: Upload a document and create a document record
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file, options]
properties:
file:
type: string
format: binary
options:
type: string
description: JSON string with filename and association fields. Verify schema in Postman.
responses:
'200':
description: Uploaded document
content:
application/json:
schema:
type: object
additionalProperties: true
/odata/Companies:
get:
operationId: listCompanies
parameters:
- name: $filter
in: query
schema: { type: string }
- name: $top
in: query
schema: { type: integer, minimum: 1 }
- name: $skip
in: query
schema: { type: integer, minimum: 0 }
responses:
'200':
description: Company collection
post:
operationId: createCompany
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/Company' }
responses:
'200': { description: Created company }
'201': { description: Created company }
/odata/Companies({id}L):
get:
operationId: getCompany
parameters:
- name: id
in: path
required: true
schema: { $ref: '#/components/schemas/MfrId' }
responses:
'200': { description: Company }
/odata/ServiceObjects:
get:
operationId: listServiceObjects
parameters:
- name: $filter
in: query
schema: { type: string }
- name: $expand
in: query
schema: { type: string }
responses:
'200': { description: Service object collection }
post:
operationId: createServiceObject
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ServiceObject' }
responses:
'200': { description: Created service object }
'201': { description: Created service object }
```
## 18. AI coding prompt to generate an SDK
Use this prompt with an AI coding tool after adding tenant-specific Postman details.
```text
Build a TypeScript SDK for the mfr Mobile Field Report API.
Requirements:
- Use Basic Auth over HTTPS.
- Make baseUrl configurable; default to https://portal.mobilefieldreport.com.
- Use /odata for entity CRUD and /mfr for special REST operations.
- Treat IDs as strings because mfr IDs can exceed JavaScript safe integer range.
- Append L to numeric OData path IDs, e.g. /odata/Companies(123L).
- Implement OData query builder supporting $filter, $select, $expand, $orderby, $top, $skip, $count.
- Implement createServiceRequestDeep using POST /mfr/ServiceRequest/Deep.
- Implement uploadDocument using multipart/form-data POST /mfr/Document/UploadAndCreate with fields file and options.
- Implement Companies, Contacts, ServiceObjects, Appointments, and ItemTypes list/get/create methods.
- Mark update/delete methods experimental unless verified in Postman.
- Include typed errors, retries for GET on 429/5xx, redacted logging, and tests for URL building.
- Do not assume exact response bodies; parse extensibly with additionalProperties.
```
## 19. Test plan
### 19.1 Unit tests
| Test | Expected result |
|---|---|
| `formatMfrId('123')` | `123L` |
| `formatMfrId('123L')` | `123L` |
| `formatMfrId('abc')` | throws validation error |
| OData string literal for `O'Brien` | `'O''Brien'` |
| Query builder with filter and expand | URL-encoded `$filter` and `$expand` parameters |
| Empty JSON response | returns `undefined` / void without JSON parse error |
| Error response with `Message` | typed error includes readable message |
| Upload document | multipart contains `file` and `options` fields |
### 19.2 Integration tests
Run these against a sandbox/test tenant:
1. Authenticate with invalid credentials and confirm 401 handling.
2. List companies with `$top=1`.
3. Create company with unique `ExternalId`.
4. Query same company by `ExternalId`.
5. Create service object linked to company.
6. Create service request with `/mfr/ServiceRequest/Deep` using a test template ID.
7. Upload a small text file to `/mfr/Document/UploadAndCreate`.
8. Attempt duplicate create and confirm idempotency behavior.
9. Test pagination with `$top` and `$skip`.
10. Verify datetime filters on appointments.
### 19.3 Production readiness checklist
- Current Postman collection exported and archived.
- Tenant-specific base URL confirmed.
- Authentication method confirmed.
- All required fields for create operations confirmed.
- Template IDs documented.
- Appointment type/user assignment semantics documented.
- Document upload `options` schema confirmed.
- Maximum upload size confirmed.
- Rate limit behavior confirmed.
- Update/delete support confirmed or intentionally disabled.
- Data protection and logging rules approved.
## 20. Known gaps to verify
The following must be verified before a production-grade implementation is declared complete:
1. Exact authentication method and whether API tokens are supported in addition to Basic Auth.
2. Current Postman collection endpoint list and examples.
3. Exact response bodies for create/update/delete and deep-create operations.
4. Whether OData returns plain arrays, `{ value: [...] }`, or another wrapper for all list endpoints.
5. Whether updates require `PUT`, `PATCH`, merge semantics, or ETags.
6. Whether delete operations are enabled for each entity.
7. Whether OData metadata (`$metadata`) is publicly available and complete.
8. Exact enum values for `State`, appointment `Type`, item `Type`, units, tags, and report definitions.
9. Exact document upload `options` schema and association model.
10. AMQP connection details and event schema.
11. UGL article import file format.
12. Navision/Dynamics kit installation and payload semantics.
## 21. Recommended next implementation sequence
1. Import the current Postman collection into the repository as documentation only.
2. Generate a minimal OpenAPI definition from verified endpoints.
3. Implement transport, authentication, error parser, and OData query builder.
4. Implement read-only list/get methods first.
5. Implement idempotent create flows using `ExternalId`.
6. Implement `/mfr/ServiceRequest/Deep` with tenant-provided template IDs.
7. Implement document upload after verifying `options` schema.
8. Add sandbox integration tests.
9. Add production-safe logging and credential handling.
10. Freeze a versioned SDK contract and publish.
## 22. cURL examples
### 22.1 List service objects with contacts
```bash
curl -u "$MFR_USERNAME:$MFR_PASSWORD" \
"https://portal.mobilefieldreport.com/odata/ServiceObjects?%24top=10&%24expand=Contacts"
```
### 22.2 Get a single service object
```bash
curl -u "$MFR_USERNAME:$MFR_PASSWORD" \
"https://portal.mobilefieldreport.com/odata/ServiceObjects(9875849220L)?%24expand=Contacts"
```
### 22.3 Create service request deeply
```bash
curl -u "$MFR_USERNAME:$MFR_PASSWORD" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-X POST \
"https://portal.mobilefieldreport.com/mfr/ServiceRequest/Deep" \
--data @deep-service-request.json
```
### 22.4 Upload document
```bash
curl -u "$MFR_USERNAME:$MFR_PASSWORD" \
-X POST \
"https://portal.mobilefieldreport.com/mfr/Document/UploadAndCreate" \
-F "file=@report.pdf;type=application/pdf" \
-F 'options={"filename":"report.pdf"}'
```
## 23. Reference URLs
- Official mfr wiki: `https://faq.mobilefieldreport.com/de/wiki/beschreibung-schnittstelle`
- Public Postman documentation supplied: `https://documenter.getpostman.com/view/6932380/2sB3dWsn6U`
- OData overview: `https://www.odata.org/`
- OData query option documentation: Microsoft Learn OData query option documentation
- Public n8n community/API discussion for document upload
- Public mfr ecosystem connector references, used only as non-authoritative implementation evidence
## 24. Final note for AI coding tools
When generating code, distinguish between **transport mechanics** and **business certainty**. The transport mechanics (Basic Auth, OData URL building, JSON requests, multipart upload) can be implemented generically. Business-specific fields, enums, template IDs, and document association fields must remain configurable until confirmed against the current tenant and the live Postman collection.