--- 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 ``` For file upload: ```http Accept: application/json Content-Type: multipart/form-data; boundary= Authorization: Basic ``` 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 ``` ## 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 { 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; uploadDocument(file: Uploadable, options: UploadDocumentOptions): Promise; // Companies listCompanies(query?: ODataQuery): Promise; getCompany(id: MfrId): Promise; findCompanyByExternalId(externalId: string): Promise; createCompany(input: MfrCompany): Promise; updateCompany(id: MfrId, input: Partial): Promise; // Contacts listContacts(query?: ODataQuery): Promise; getContact(id: MfrId): Promise; // Service objects listServiceObjects(query?: ODataQuery): Promise; getServiceObject(id: MfrId, expand?: string[]): Promise; createServiceObject(input: MfrServiceObject): Promise; // Appointments listAppointments(query?: ODataQuery): Promise; getAppointment(id: MfrId): Promise; createAppointment(input: MfrAppointment): Promise; // Item types listItemTypes(query?: ODataQuery): Promise; getItemType(id: MfrId): Promise; findItemTypeByExternalId(externalId: string): Promise; createItemType(input: MfrItemType): Promise; } ``` ### 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.