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

40 KiB

title, subtitle, author, date
title subtitle author date
mfr Mobile Field Report - Interface Description for AI Coding Tools REST/OData integration contract derived from the public mfr wiki, public Postman documentation context, and public implementation evidence Prepared for AI-assisted development 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:

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.

Do not hard-code the host. Provide configuration:

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:

MFR_BASE_URL="https://portal.mobilefieldreport.com"
MFR_USERNAME="service-account@example.com"
MFR_PASSWORD="***"

5.2 Default headers

For JSON operations:

Accept: application/json
Content-Type: application/json
Authorization: Basic <base64(username:password)>

For file upload:

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:

/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:

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

/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:

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:

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

export interface MfrLocation {
  Id?: MfrId;
  AddressString?: string;
  Postal?: string;
  City?: string;
  Country?: string;       // e.g. DE
  Latitude?: number;
  Longitude?: number;
}

7.4 Contact model

export interface MfrContact {
  Id?: MfrId;
  FirstName?: string;
  LastName?: string;
  Telephone?: string;
  MobilePhone?: string;
  Email?: string;
  CompanyId?: MfrId;
}

7.5 Service object model

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

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

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

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

POST /mfr/ServiceRequest/Deep
Host: portal.mobilefieldreport.com
Content-Type: application/json
Accept: application/json

Full observed URL:

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

{
  "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:

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

POST /mfr/Document/UploadAndCreate
Host: portal.mobilefieldreport.com
Content-Type: multipart/form-data
Accept: application/json

Full observed URL:

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

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:

export interface UploadDocumentOptions {
  filename?: string;
  serviceRequestId?: MfrId;
  jobId?: MfrId;
  serviceObjectId?: MfrId;
  entityId?: MfrId;
  entityType?: string;
  description?: string;
  [key: string]: unknown;
}

Then expose:

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.

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:

{
  "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.

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:

{
  "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.

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:

{
  "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.

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.

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:

{
  "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.

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:

{
  "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"
}

Older public Postman/OData snippets show relationship operations like:

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:

{
  "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:

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:

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:

const startUtc = zonedTimeToUtc('2026-06-10 08:00', 'Europe/Berlin').toISOString();

Payload example:

{
  "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

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

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

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.

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.

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.
  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

curl -u "$MFR_USERNAME:$MFR_PASSWORD" \
  "https://portal.mobilefieldreport.com/odata/ServiceObjects?%24top=10&%24expand=Contacts"

22.2 Get a single service object

curl -u "$MFR_USERNAME:$MFR_PASSWORD" \
  "https://portal.mobilefieldreport.com/odata/ServiceObjects(9875849220L)?%24expand=Contacts"

22.3 Create service request deeply

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

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.