SitterIT Mobigo API — integration guide

Read and write in Mobigo over HTTP: tasks, customers, products, quotes, orders, contracts, time, materials and documents.

Getting started

You need a clientId and an API key from whoever operates Mobigo. The key is shown once, when the integration is created.

First call

curl -H "Authorization: Bearer mk_..." \
     "https://mobigo-api.kundens-server.se/v1/whoami"

# → {"integration":"Webshop","tenant":"Demo","scopes":["tasks:read","tasks:write"]}

/v1/whoami tells you which integration the key belongs to and what it may do.

Create a task with materials and time

curl -X POST "https://mobigo-api.kundens-server.se/v1/tasks" \
     -H "Authorization: Bearer mk_..." \
     -H "Idempotency-Key: order-4711" \
     -H "Content-Type: application/json" \
     -d '{
           "customerNr": "10003",
           "taskText":   "Lift service",
           "materials":  [{ "productNr": "A10001", "quantity": 3 }],
           "times":      [{ "timeType": "Normal time", "hours": 2 }]
         }'

# → {"nr":"U039071","id":105933,
#     "materials":[{"id":44821,"productNr":"A10001"}],
#     "times":[{"id":100636,"timeType":"Normal time"}]}

The response carries an id for every line created. With that id the line can be updated directly through PATCH /v1/materials/{id} or PATCH /v1/times/{id}.

All or nothing. If Mobigo rejects any line the whole call is rolled back and no task remains. The response names the line: materials[1]: Ingen artikel med nummer "FINNS-EJ".

Authentication

Everything under /v1 requires Authorization: Bearer <value>. The value is either the key itself or a token. Both give the same company, the same permissions and the same IP restriction.

ValueWhatLifetime
Bearer mk_…The API key Until revoked
Bearer mt_…Token from POST /v1/token At most 60 minutes
Use a token in production. With Bearer mk_… the long-lived secret travels with every call. Anything that logs headers along the way — a reverse proxy, an APM tool, a shared Postman collection — then captures a key that is valid until someone revokes it. A token leaked the same way dies within the hour.

The key itself is simpler and perfectly reasonable while you are trying things out, and when the calls come from inside the same server environment.

Tokens are fetched with client_credentials:

An installation may require tokens. When the switch is on in the admin page, Bearer mk_… is no longer accepted — the key is only sent to /v1/token as clientSecret. Calls using the key as a Bearer answer 401 with token_required and say what to do instead.
curl -X POST "https://mobigo-api.kundens-server.se/v1/token" \
     -H "Content-Type: application/json" \
     -d '{"clientId":"f287ea5c…","clientSecret":"mk_…"}'

# → {"access_token":"mt_…","token_type":"Bearer","expires_in":3600,
#    "scope":"tasks:read tasks:write"}

Fetch a new one when expires_in has passed, or when a call answers 401. There is no refresh token — fetching is one call, and costs less than keeping track of two kinds of token.

Where does the key come from?

It is created by whoever operates Mobigo, on the server, with SitterIT.Mobigo.Api keys create. You cannot create it yourself — by design: the key is tied to a company and to a Mobigo signature, and both are the customer's decisions to make.

Ask for three things:

You getWhich is
The addresse.g. https://mobigo-api.kundens-server.se
clientId The integration's id. Only needed for POST /v1/token
The API key mk_… Shown once when the integration is created, and never retrievable again

Say at the same time which scopes you need — they are set when the key is created and cannot be widened afterwards without a new key.

Scopes

Every resource has two: <resource>:read and <resource>:write. Without the scope the API answers 403.

On top of them, Mobigo's own permissions apply to the signature the key works as. A wide scope therefore never grants more than that signature may do in Mobigo.

Tasks

POST /v1/tasks accepts the task's whole writable surface. Only customerNr is required. An omitted field is left untouched.

FieldTypeDescription
customerNrtext Required. Must exist in Mobigo
nrtext Task number. Omitted, Mobigo's number series takes over
statustext Planned, Sent, Received, InProgress, Finished or Archived. Omitted gives Mobigo's default status. See Status
taskTexttextTask text
taskNotetextTask note
actionTexttextAction text
actionNotetextAction note
customerOrderertextWho ordered, at the customer
customerReftextThe customer's reference
ownerSigntext Owner. Omitted, the integration's own signature is used
userSignslist Assigned users. Every signature must exist
planStartDatedate Start alone means one day
planStopDatedateEnd of the planned period
planDateTypetext FromTo, Day, Week, Month or Fixed. Dates without a time of day give Day; with one, FromTo. See Planned dates and times
estimatedTimenumberEstimated hours
projectNrtextProject. Must exist
priorityNametext The priority's name in the customer's Mobigo
priceListNrtext Price list. Drives the pricing of the lines
object1Nrobject4Nrtext The customer's own objects. The name is taken from the record
customT1customT3text Own text fields
customA1customA3text Own note fields
extraFieldsobject The customer's own fields. See Extra fields
materialslistMaterial lines in the same call
timeslistTime lines in the same call
curl -X POST "https://mobigo-api.kundens-server.se/v1/tasks" \
     -H "Authorization: Bearer mk_..." \
     -H "Idempotency-Key: order-4712" \
     -H "Content-Type: application/json" \
     -d '{
           "customerNr":      "10003",
           "taskText":        "Half-year service, lift 3",
           "taskNote":        "Customer wants notice the day before.",
           "customerRef":     "Order 88214",
           "status":          "Sent",
           "ownerSign":       "AN",
           "userSigns":       ["AN", "PL"],
           "planStartDate":   "2026-09-14",
           "planStopDate":    "2026-09-16",
           "planDateType":    "FromTo",
           "estimatedTime":   6.5,
           "projectNr":       "P-204",
           "priorityName":    "High",
           "object1Nr":       "LIFT-0031",
           "customT1":        "Service contract",
           "extraFields":     { "Visma_Ordernr.": "VO-4711" },
           "materials":       [{ "productNr": "A10001", "quantity": 3 }],
           "times":           [{ "timeType": "Normal time", "hours": 4 }]
         }'

Status

Status can be set in the same call that creates the task with the status field, or afterwards with POST /v1/tasks/{nr}/status. Both go through Mobigo's own transition rules — the API has none of its own.

On create the status is applied last, after assignees, extra fields and lines: Mobigo's rules read the task, and they should read the finished one. If Mobigo refuses, the whole task is rolled back, lines and all, and the API answers 422 with the reason.

The response to POST /v1/tasks always carries status, also when you did not ask for one.

An owner is required for status. Without one Mobigo refuses status changes with "An owner must be specified". An omitted ownerSign becomes the integration's own signature, so it usually resolves itself — but not if you explicitly send an empty one.

Planned dates and times

A planned date is an interval with a type in Mobigo, not a date. Only two of the types carry a time of day: FromTo and Fixed. Day, Week and Month are dates, and have no clock time.

Leave planDateType out and the dates decide: plain dates give Day, dates with a time of day give FromTo.

{ "planStartDate": "2026-09-15T08:00:00",
  "planStopDate":  "2026-09-15T12:00:00" }   → FromTo 08:00–12:00

{ "planStartDate": "2026-09-15" }            → Day

Name a type that carries no time and send a clock time anyway, and the API answers 422 with FromTo and Fixed in validValues. The time used to be cut off silently, leaving the task on the whole day with nothing to say so.

Leave out planStopDate and the planned period runs to the end of the day. Day is one day: a planStopDate further ahead is not used. For a span across several days, name FromTo.

Updating a task

PATCH touches only the fields you send. The rest are left as Mobigo has them.

curl -X PATCH "https://mobigo-api.kundens-server.se/v1/tasks/U039079"      -H "Authorization: Bearer mk_..."      -H "Idempotency-Key: $(uuidgen)"      -H "Content-Type: application/json"      -d '{ "taskText":   "Replace the gasket too",
           "planStartDate": "2026-09-15",
           "userSigns":     ["AN", "KJ"],
           "extraFields": { "Visma_Ordernr.": "VO-4712" } }'

userSigns replaces the assignment — it does not add to it. An omitted list touches nothing, [] clears it, and an assignee who stays on the list keeps their own status on the task. The owner is a separate field, ownerSign.

When a task goes from one assignee to several, Mobigo itself puts [the signature] at the front of actionText, so it is visible whose text it was before the task was shared. That is Mobigo's own marking, the same one you get when the change is made in Desktop — not something the API adds.

The response is the whole task after the change — the same shape as GET /v1/tasks/{nr}. Change the price list and you see straight away what it did to the lines.

Customer and status are not changed with PATCH. The customer cannot be changed at all: the price list, objects and contacts hang off it, and a change would leave the task pointing at the wrong company. Status has its own path for the reason above. A field can be cleared with "", but not with null — omitted and null both mean "leave alone".

Orders and quotes

Both are created with their lines in one call. If any line fails the whole record is rolled back — half an order looks like a real one and never gets completed.

An order with lines

curl -X POST "https://mobigo-api.kundens-server.se/v1/orders" \
     -H "Authorization: Bearer mk_..." \
     -H "Idempotency-Key: order-B-4711" \
     -H "Content-Type: application/json" \
     -d '{
           "customerNr":       "10018",
           "supplierNr":       "L00001",
           "userSign":         "AN",
           "customerOrderer":  "Anna Beställare",
           "orderDate":        "2026-08-20",
           "deliveryDate":     "2026-09-14",
           "deliveryName":     "Ekehamn Bygg AB",
           "deliveryAddressLine1": "Verkstadsgatan 8",
           "deliveryPostalCode":   "417 05",
           "deliveryCity":         "Göteborg",
           "internalNote":     "Call before delivery",
           "lines": [
             { "productNr": "10053",  "quantity": 5, "supplierNr": "L00001" },
             { "productNr": "A10001", "quantity": 2 },
             { "productNr": "A10002", "quantity": 1, "price": 1250 }
           ]
         }'

# → { "Nr": "B00065", "Status": "Planned", "CustomerNr": "10018",
#     "SupplierNr": "L00001", "OrderDate": "2026-08-20T00:00:00", … }

The number comes from Mobigo's number series. orderDate defaults to today, and the product's unit and name are taken from the product register.

The line inherits the order. Price list, delivery date and supplier are taken from the order when the line does not state them — the same thing Mobigo does when a line is added in the client. Give a line its own supplierNr only when that line is bought from someone else.
Order lines are not priced automatically. Unlike material on a task, Mobigo puts no price on an order line. Omit price and the line stands at zero.

Lines on an existing order

POST https://mobigo-api.kundens-server.se/v1/orders/B00065/lines
Idempotency-Key: line-B00065-4

{ "productNr": "A10003", "quantity": 2, "deliveryDate": "2026-09-20" }

# → { "id": 10011, "orderNr": "B00065", "productNr": "A10003", "totalPrice": 1900 }

Changing an order

PATCH touches only the fields you send. The rest are left as Mobigo has them.

curl -X PATCH "https://mobigo-api.kundens-server.se/v1/orders/B00065" \
     -H "Authorization: Bearer mk_..." \
     -H "Idempotency-Key: order-B00065-delivery" \
     -H "Content-Type: application/json" \
     -d '{ "deliveryDate": "2026-09-21",
           "internalNote": "Delivery moved, customer informed" }'

Individual lines are changed by their id:

PATCH https://mobigo-api.kundens-server.se/v1/orderlines/10011
Idempotency-Key: line-10011-quantity

{ "quantity": 3, "discountPct": 10 }
Status is not changed with PATCH. It follows Mobigo's own rules, the same way it does on a task.

A quote with materials and time

A quote carries material and time lines, like a task — not order lines.

curl -X POST "https://mobigo-api.kundens-server.se/v1/quotes" \
     -H "Authorization: Bearer mk_..." \
     -H "Idempotency-Key: quote-4711" \
     -H "Content-Type: application/json" \
     -d '{
           "customerNr":  "10018",
           "description": "Lift service, half year",
           "ownerSign":   "AN",
           "materials": [
             { "productNr": "A10001", "quantity": 3 },
             { "productNr": "A10002", "quantity": 1 }
           ],
           "times": [
             { "timeType": "Normal time", "hours": 4 },
             { "timeType": "Normal time", "hours": 2.5 }
           ]
         }'

quoteDate defaults to today and the validity to 30 days when omitted. Get the time types' names from GET /v1/timetypes.

Reading the lines

Material and time lines hang off different kinds of record. ownerType says which — task, quote, order or contract. Omitted together with ownerNr, a task is assumed.

GET https://mobigo-api.kundens-server.se/v1/materials?ownerType=quote&ownerNr=Q00005
GET https://mobigo-api.kundens-server.se/v1/times?ownerType=quote&ownerNr=Q00005
GET https://mobigo-api.kundens-server.se/v1/orderlines?orderNr=B00065

Every line says what it hangs off, in OwnerNr and OwnerType.

Absence and calendar bookings

Holiday, sick leave, care of a sick child, doctor's appointments and private time belong to no task. In Mobigo they are calendar bookings: time entries with no owner. Create them with POST /v1/times, not under a task.

curl -X POST "https://mobigo-api.kundens-server.se/v1/times" \
     -H "Authorization: Bearer mk_..." \
     -H "Idempotency-Key: absence-JV-2026-07" \
     -H "Content-Type: application/json" \
     -d '{
           "timeType":  "Frånvaro / Semester",
           "userSign":  "JV",
           "date":      "2026-07-02T08:00:00",
           "stopDate":  "2026-07-24T17:00:00",
           "hours":     17,
           "spec":      ""
         }'

The response is the entry, with ownerNr and ownerType as null — that is what makes it standalone.

One entry for the period, not one per day

Give both date and stopDate. The absence becomes ONE entry: 08:00 on the first day to 17:00 on the last.

stopDate is written verbatim, so a GET on the entry returns exactly what you sent. For a pure all-day entry — both values at midnight — Mobigo's own convention is that the stop falls on the day after the last day.

"date": "2026-07-02T08:00:00", "stopDate": "2026-07-24T17:00:00"   ← clock times, literal
"date": "2026-07-02T00:00:00", "stopDate": "2026-07-25T00:00:00"   ← all-day through the 24th
hours is stored as you send it, in the time type's own unit. Holiday is counted in days, so 17 becomes 17 days. The API never derives the quantity from the clock times, and a given stopDate wins over the stop a summable time type would otherwise have computed.

The time type must be a booking type — going forward

If the entry starts tomorrow or later, the time type must be marked as a booking type in Mobigo. Same rule the client applies. Today and backwards there is no such restriction — that is where internal time gets registered after the fact.

If it is not, the answer is 422 with the company's booking time types in validValues. Which ones exist is in GET /v1/timetypes, on the BookingType field.

Changing an entry

PATCH /v1/times/{id} changes the entry, stopDate included — so a shortened holiday can be corrected through the API.

ownerNr cannot be set here. A time entry that belongs to a task is created on the task, with POST /v1/tasks/{nr}/times. Sending ownerNr to POST /v1/times gets a 400 rather than silently creating a floating entry.

Idempotency-Key

A call can arrive without its response getting back. Without protection the retry gives the customer two identical tasks.

Idempotency-Key is a string you choose per operation. When the same key comes back with the same content, the API answers with the first response instead of doing the work again.

The response echoes the key back and says whether it is a stored response. The echo is present on error responses too.

Idempotency-Key: order-4711
Idempotency-Replayed: false     ← the call ran now
Idempotency-Replayed: true      ← you got the receipt for an earlier call
Idempotency-Key: order-4711   → {"nr":"U039075"}
Idempotency-Key: order-4711   → {"nr":"U039075"}   ← same task, nothing new
Idempotency-Key: order-4712   → {"nr":"U039076"}   ← new order, new task
Idempotency-Key: order-4711 + changed content → 409
without the header                            → 400
Use your own order number, not a random GUID per attempt. A new GUID every time creates a new task every time and protects against nothing.

Every writing call requires the header: POST and PATCH, on all resources.

Paging

The response looks the same for every resource:

{ "items": [ … ], "nextCursor": "105935", "page": 1, "total": 65233 }

limit defaults to 50 and is capped at 500. total is null for the resources Mobigo cannot count.

Cursor

Constant speed however deep you go. Use it when syncing.

GET https://mobigo-api.kundens-server.se/v1/customers?limit=500
# → "nextCursor": "105935"
GET https://mobigo-api.kundens-server.se/v1/customers?limit=500&cursor=105935

nextCursor is where the previous page ended. null means there is nothing more to fetch.

Page number

GET https://mobigo-api.kundens-server.se/v1/customers?limit=50&page=3
Pages drift. page counts from the start of the result list, and the list is sorted newest first. If ten records are created while you page, every older row is pushed ten steps down, so the next page starts with rows you already fetched. The cursor does not count — it continues from the last row you got — and is therefore immune.

Fetch only what changed

GET https://mobigo-api.kundens-server.se/v1/tasks?modifiedSince=2026-08-19T00:00:00Z

Store Modified from the previous run and ask for everything changed since.

Contacts and addresses

Contact persons and extra addresses are records of their own, hanging off a customer, a supplier or an object. They are identified by Id, not by a number.

Contacts on one customer

GET https://mobigo-api.kundens-server.se/v1/contacts?customerNr=10003

# → { "items": [ { "Id": "11311", "CustomerNr": "10003", "Name": "Linus Sedin",
#                  "Position": "Property manager", "Mobile": "070-1234567",
#                  "Email": "[email protected]", "MostImportant": true } ],
#     "nextCursor": null, "page": 1 }

All contacts

Without customerNr the whole register is listed. name is a free text search on the name.

GET https://mobigo-api.kundens-server.se/v1/contacts?limit=500
GET https://mobigo-api.kundens-server.se/v1/contacts?name=Sedin
GET https://mobigo-api.kundens-server.se/v1/contacts?modifiedSince=2026-08-01T00:00:00Z

Adding a contact

POST https://mobigo-api.kundens-server.se/v1/contacts
Idempotency-Key: contact-10003-linus

{ "customerNr": "10003", "name": "Linus Sedin",
  "position": "Property manager", "email": "[email protected]",
  "mobile": "070-1234567", "mostImportant": true }

Contacts when the customer is created

Send them in the same call. If any contact is rejected the whole customer is rolled back.

POST https://mobigo-api.kundens-server.se/v1/customers
Idempotency-Key: customer-storfors

{
  "name": "Storfors Service AB",
  "addressLine1": "Storgatan 1",
  "postalCode": "412 50",
  "city": "Göteborg",
  "contacts": [
    { "name": "Linus Sedin", "position": "Property manager",
      "email": "[email protected]", "mostImportant": true },
    { "name": "Anna Ek", "position": "Finance", "email": "[email protected]" }
  ]
}

The number was omitted, so Mobigo's number series assigned it. The response contains the customer with its Nr.

Supplier contacts

Same fields, own path: /v1/suppliercontacts with supplierNr.

Addresses

Extra addresses — delivery, invoice, visiting. The owner must be given, because the address table is shared between customers, suppliers and objects.

GET https://mobigo-api.kundens-server.se/v1/addresses?customerNr=10003
GET https://mobigo-api.kundens-server.se/v1/addresses?objectNr=O1-11840&level=1

Extra fields

The customer's own fields, defined in their Mobigo. They are named whatever the customer called them and always arrive as strings.

Which fields exist?

Every resource describes its extra fields: key, type, whether it can be written and which values a list field allows.

GET https://mobigo-api.kundens-server.se/v1/customers/extra-fields

# → { "items": [
#       { "Key": "Avtals_typ", "Name": "Contract type", "Category": "Contract",
#         "Type": "Combo", "Writable": true, "ReadOnly": false, "Computed": false,
#         "Options": ["Basic service", "Incentive", "On call"] },
#       { "Key": "Startdatum_avtal", "Name": "Contract start",
#         "Type": "Date", "Writable": true },
#       { "Key": "Region", "Name": "Region",
#         "Type": "Sql", "Writable": false, "Computed": true }
#     ],
#     "count": 59 }

Writable: false means a write is rejected — the field is either read only or computed by Mobigo. Options is present on list fields only.

The same path exists on every resource, tasks included: GET https://mobigo-api.kundens-server.se/v1/tasks/extra-fields.

Reading

GET https://mobigo-api.kundens-server.se/v1/customers/10003

# → {
#     "Nr": "10003",
#     "Name": "Storfors Service AB",
#     "ExtraFields": {
#       "Avtals_typ": "Helservice",
#       "Startdatum_avtal": "2024-01-01",
#       "Index_regleras": ""
#     }
#   }

Empty fields are included as empty strings, so you can see which fields exist. That is also how you get the exact names.

Writing on a resource

curl -X PATCH "https://mobigo-api.kundens-server.se/v1/customers/10003" \
     -H "Authorization: Bearer mk_..." \
     -H "Idempotency-Key: sync-10003-v4" \
     -H "Content-Type: application/json" \
     -d '{ "extraFields": { "Avtals_typ": "Delservice", "Avtals_nr": "A-9912" } }'

Writing on a task

The same object, in the same call that creates the task:

curl -X POST "https://mobigo-api.kundens-server.se/v1/tasks" \
     -H "Authorization: Bearer mk_..." \
     -H "Idempotency-Key: order-4713" \
     -H "Content-Type: application/json" \
     -d '{
           "customerNr":  "10003",
           "taskText":    "Service",
           "extraFields": {
             "Visma_Ordernr.": "VO-4711",
             "Avtals_typ":     "Helservice",
             "Startdatum":     "2026-09-14"
           }
         }'

The key is the field's name in the customer's Mobigo, exactly as it appears in ExtraFields when you read the record. Dots and underscores are part of the name. The value is always a string, dates and numbers included.

Errors you may get

ResponseMeaning
Posten har inget extrafält som heter "X". Giltiga: … The fields are defined in Mobigo. The response lists the valid names
Extrafältet "Region" beräknas av Mobigo (typ Sql) och går inte att skriva till. The field is computed at display time. A value sent there disappears silently
Värdet "imorgon" passar inte extrafältet "TestDatum", som är av typen Date. Mobigo silently discards values it cannot parse. The API reads back and objects

Lookups

The values other calls require. They belong to the customer, so they cannot be listed in advance. All of them are read only — they are set in Mobigo's administration.

PathGivesNeeded for
GET /v1/usersSignatures and names ownerSign, userSigns, userSign
GET /v1/timetypesThe time types' names timeType on a time line, and which ones work for absence
GET /v1/pricelistsThe price lists' numbers priceListNr
GET /v1/field-namesThe customer's labels for standard fields showing fields the user recognises
GET https://mobigo-api.kundens-server.se/v1/timetypes

# → { "items": [
#       { "Name": "Normal time", "Unit": "h", "BasePrice": 750,
#         "InternalType": false, "BookingType": false,
#         "SpecOptions": ["Diagnosis", "Replacement"] },
#       { "Name": "Frånvaro / Semester", "Unit": "dgr", "BasePrice": 0,
#         "InternalType": true, "BookingType": true, "SpecOptions": [] }
#     ] }

BookingType means the time type works for a standalone calendar booking. The name is the whole string, slash included: in Mobigo Frånvaro / Semester is the folder Frånvaro and the type Semester, but the API wants it written out in full.

The customer's own field names

Custom1Custom4 are called something else at the customer. /v1/field-names says what.

GET https://mobigo-api.kundens-server.se/v1/field-names

# → { "items": [
#       { "DbFieldName": "CustCustom1", "UserFieldName": "Postal address",
#         "ListOnly": false, "Hidden": false },
#       { "DbFieldName": "TaskCustom1", "UserFieldName": "Service contract",
#         "ListOnly": true, "Hidden": false }
#     ] }

DbFieldName is the name this API uses. UserFieldName is the customer's label. Hidden means the field is not shown in Mobigo.

Location

Mobigo geocodes its own records, against the map service and the rules the customer has set up. This API reads that position and can ask Mobigo to recalculate it, but never geocodes on its own.

GET https://mobigo-api.kundens-server.se/v1/tasks/U039079/location

# → { "Latitude": 57.7089, "Longitude": 11.9746,
#     "Address": "Storgatan 1, 412 50 Göteborg",
#     "Status": "Ok", "SourceType": "Address", "Provider": "GoogleMaps",
#     "Fixed": false, "Ambiguous": false }

?locate=true recalculates the position first, from the task's customer, object or address. ?force=true redoes it even when a position already exists.

Latitude and Longitude are null when the record has no position. Status says why: NoAddress, AddressNotFound, AddressInvalid, Pending or Error.

Fixed position

For addresses that cannot be looked up. A fixed position is never overwritten by Mobigo's own geocoding.

PUT https://mobigo-api.kundens-server.se/v1/tasks/U039079/location
Idempotency-Key: pos-U039079

{ "latitude": 57.7089, "longitude": 11.9746,
  "address": "Building site, Ringön" }

The same two paths exist on every resource: /v1/customers/10003/location, /v1/objects1/O1-11840/location and so on.

Documents on a task

Three calls: attach, list and fetch. The list and the content are separate — a task can carry attachments of tens of megabytes.

POST /v1/tasks/U039079/documents
Idempotency-Key: attachment-4711

{ "name": "workorder.pdf", "contentBase64": "JVBERi0xLjQK..." }

201 { "id": 60728, "nr": "U039079", "name": "workorder.pdf" }
GET /v1/tasks/U039079/documents

{ "items": [ { "Id": 60728, "Name": "workorder.pdf", "DocType": "EMBED",
               "Kind": "embedded", "IsInternal": false, "HasContent": true,
               "Modified": "2026-08-19T22:41:23.72" } ],
  "count": 1 }
GET /v1/tasks/U039079/documents/60728

200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename=workorder.pdf
<the file's content>

Linked documents and print templates

Kind says what the document is:

KindWhat it isWhat you get
embeddedThe file is stored in Mobigothe file as is
linkPoints at a path outside Mobigo the file, fetched from that path
htmlHTML content in MobigoPDF
otherOnly has a print template PDF, rendered from the template

Mobigo's own document handling does the work in all four cases. That is why Content-Type can be application/pdf even when the file name ends in something else — the type comes from the content, not from the name. If the name has no extension, the right one is added in Content-Disposition.

A link whose path is unreachable from the server gives 422 saying so, not an empty file.

Error codes

All errors are application/problem+json per RFC 9457.

{ "type": "https://sitterit.se/problems/command_failed",
  "title": "command_failed",
  "status": 422,
  "detail": "Ingen artikel med nummer FINNS-EJ." }
CodeMeaningWhat you do
400The call is wrong: missing Idempotency-Key, unknown field name, malformed JSONFix the call
401The key is missing, wrong or revoked Check the key
403The key is valid but lacks the scope Ask for a wider scope
409Idempotency-Key reused with different content Use a new key
413The request is larger than 32 MB. Base64 makes documents about a third larger than the fileSend fewer or smaller documents
404The task, order or record you addressed in the path does not exist (not_found)Check the number
422The call was well formed but Mobigo said no. The text is Mobigo's own Read the text
429Too many calls Wait per Retry-After
503The engine is down (engine_unreachable) or full (engine_busy)Retry per Retry-After
504The engine did not answer in time (engine_timeout) Retry with the same Idempotency-Key
503 and 504 are not the same. On 503 the engine never received the call and a retry is safe. On 504 it may have written before it stopped answering — retry with the same Idempotency-Key.

Valid values: validValues

When a value is rejected — a time type name, a status, a field name — the error response carries the list of accepted values in validValues.

{ "type": "https://sitterit.se/problems/command_failed",
  "title": "command_failed",
  "status": 422,
  "detail": "Ingen tidtyp som heter \"Taxa 2\". 36 giltiga namn finns i validValues, hela listan i GET /v1/timetypes.",
  "validValues": ["Administrativ tid", "Fastpris", "Service, debiterbar", "…"],
  "requestId": "94cc0d1aa31a44a0" }

It is a list, not a sentence to parse. Names contain commas — Service, debiterbar is a single time type — and a comma-separated string cannot be split reliably. Read the list, populate a dropdown, validate against it before sending.

The field is present only when the error concerns a rejected value and the list is known. A 404 or 503 does not carry it.

Tracing: X-Request-Id

Every response carries X-Request-Id. Error responses have the same value in the requestId field. Quote it in support cases.

{ "type": "https://sitterit.se/problems/command_failed",
  "title": "command_failed",
  "status": 422,
  "detail": "Ingen artikel med nummer FINNS-EJ.",
  "requestId": "8d1f55fc02754b1e" }

If you send your own X-Request-Id it is used instead and echoed back. Allowed characters are letters, digits and - _ . :, at most 64.

Trail in Mobigo: the change log

Every write adds a row to Mobigo's own change log — the one shown on the record in Desktop. The row carries the integration's signature and says via API, so it can be told apart from what someone did by hand.

Skapade tidrad via API, Administrativ tid
Ändrade kund via API

Lines on a record — time, materials, order lines, contacts, addresses — are logged on the owner: the task, the order or the customer. That is where Desktop puts its own rows, and where anyone looks for what happened.

This is Mobigo's log, not the API's. The API's own audit log of calls and keys is described in the operations guide.

Which register is it?

The names in Swagger are the API's. The names in the last column are Mobigo's own, from the customer's own client — so you can hold the two windows side by side and know you are looking at the same thing.

SwaggerEndpointRegister in Mobigo
Tasks/v1/tasksTasks (sv: Uppdrag)
Customers/v1/customersCustomers (sv: Kunder)
Products/v1/productsProducts (sv: Artiklar)
Suppliers/v1/suppliersSuppliers (sv: Leverantörer)
Quotes/v1/quotesQuotes (sv: Offerter)
Orders/v1/ordersOrders (sv: Order)
Order lines/v1/orderlinesOrder lines (sv: Orderrader)
Contracts/v1/contractsContracts (sv: Avtal)
Resources/v1/resourcesResources (sv: Resurser)
Projects/v1/projectsProjects (sv: Projekt)
Contacts/v1/contactsContacts (sv: Kontaktpersoner)
Supplier contacts/v1/suppliercontactsContacts (suppliers) (sv: Kontaktpersoner (lev))
Addresses/v1/addressesAddresses (sv: Adresser)
Users/v1/usersUsers (sv: Användare)
Time types/v1/timetypesTimetypes (sv: Tidtyper)
Price lists/v1/pricelistsPrice lists (sv: Prislistor)
Objects 1/v1/objects1Object 1 (sv: Objekt 1)
Objects 2/v1/objects2Object 2 (sv: Objekt 2)
Objects 3/v1/objects3Object 3 (sv: Objekt 3)
Objects 4/v1/objects4Object 4 (sv: Objekt 4)
Times/v1/timesTimes (sv: Tider)
Materials/v1/materialsMaterials (sv: Material)

Writing on a task

These do not write in a register of their own — they add to the task.

EndpointEnds up in Mobigo as
POST /v1/tasks/<nr>/statusThe task's status, through Mobigo's own transition rules
POST /v1/tasks/<nr>/notesA log entry on the task (Log events)
POST /v1/tasks/<nr>/timesA row in Times, owned by the task
POST /v1/tasks/<nr>/materialsA row in Materials, owned by the task
POST /v1/tasks/<nr>/documentsA document on the task (Documents)

Object levels 1–4 are named by the customer in their own Mobigo — Site, Property, Vehicle. GET /v1/field-names returns the names that installation uses.

Resources

Every resource has the same shape: GET /v1/<resource> for a list, GET /v1/<resource>/<key> for one record, and where allowed POST and PATCH.

customers products suppliers quotes orders orderlines contracts resources projects contacts suppliercontacts addresses users timetypes pricelists objects1 objects2 objects3 objects4 times materials

customers — customer

GET /v1/customers, GET /v1/customers/<Nr>, POST /v1/customers, PATCH /v1/customers/<Nr>
Scopes: customers:read, customers:write
Filters: modifiedSince, modifiedBefore, name, category

FieldTypeWritable
Nrtextyes
Statustext
Nametextyes
Categorytextyes
AddressLine1textyes
AddressLine2textyes
PostalCodetextyes
Citytextyes
Statetextyes
Countrytextyes
Phonetextyes
Faxtextyes
Emailtextyes
Custom1textyes
Custom2textyes
Custom3textyes
Custom4textyes
Notetextyes
Modifieddate (ISO 8601)
ExtraFieldsobject, string → stringyes

products — product

GET /v1/products, GET /v1/products/<Nr>, POST /v1/products, PATCH /v1/products/<Nr>
Scopes: products:read, products:write
Filters: modifiedSince, modifiedBefore, name, category

FieldTypeWritable
Nrtextyes
Statustext
Nametextyes
Categorytextyes
Unittextyes
BasePricenumberyes
PurchasePricenumberyes
CostPricenumberyes
Barcodetextyes
Custom1textyes
Custom2textyes
Custom3textyes
Custom4textyes
Notetextyes
Modifieddate (ISO 8601)
ExtraFieldsobject, string → stringyes

suppliers — supplier

GET /v1/suppliers, GET /v1/suppliers/<Nr>, POST /v1/suppliers, PATCH /v1/suppliers/<Nr>
Scopes: suppliers:read, suppliers:write
Filters: modifiedSince, modifiedBefore, name, category

FieldTypeWritable
Nrtextyes
Statustext
Nametextyes
Categorytextyes
AddressLine1textyes
AddressLine2textyes
PostalCodetextyes
Citytextyes
Statetextyes
Countrytextyes
Phonetextyes
Faxtextyes
Emailtextyes
Custom1textyes
Custom2textyes
Custom3textyes
Custom4textyes
Notetextyes
Modifieddate (ISO 8601)
ExtraFieldsobject, string → stringyes

quotes — quote

GET /v1/quotes, GET /v1/quotes/<Nr>, POST /v1/quotes, PATCH /v1/quotes/<Nr>
Scopes: quotes:read, quotes:write
Filters: modifiedSince, modifiedBefore, status, customerNr

FieldTypeWritable
Nrtextyes
Statustext
CustomerNrtextyes
CustomerNametext
CustomerOrderertextyes
CustomerReftextyes
Descriptiontextyes
Notetextyes
OwnerSigntextyes
QuoteDatedate (ISO 8601)yes
ValidUntilDatedate (ISO 8601)yes
AcceptedDatedate (ISO 8601)yes
PlanStartDatedate (ISO 8601)yes
PlanStopDatedate (ISO 8601)yes
PlanDateTypetextyes
Object1Nrtextyes
Object1Nametext
Object2Nrtextyes
Object2Nametext
Object3Nrtextyes
Object3Nametext
Object4Nrtextyes
Object4Nametext
CreatedBytext
CreatedDatedate (ISO 8601)
Custom1textyes
Custom2textyes
Custom3textyes
Custom4textyes
Modifieddate (ISO 8601)
ExtraFieldsobject, string → stringyes

orders — order

GET /v1/orders, GET /v1/orders/<Nr>, POST /v1/orders, PATCH /v1/orders/<Nr>
Scopes: orders:read, orders:write
Filters: modifiedSince, modifiedBefore, status, customerNr, supplierNr

FieldTypeWritable
Nrtextyes
Statustext
CustomerNrtextyes
CustomerNametext
CustomerOrderertextyes
SupplierNrtextyes
UserSigntextyes
OrderDatedate (ISO 8601)yes
DeliveryDatedate (ISO 8601)yes
DeliveryNametextyes
InternalNotetextyes
Notetextyes
DeliveryAddressLine1textyes
DeliveryAddressLine2textyes
DeliveryPostalCodetextyes
DeliveryCitytextyes
DeliveryStatetextyes
DeliveryCountrytextyes
DeliveryAddressIdtextyes
CreatedBytext
CreatedDatedate (ISO 8601)
Custom1textyes
Custom2textyes
Custom3textyes
Custom4textyes
Modifieddate (ISO 8601)
ExtraFieldsobject, string → stringyes

orderlines — order line

GET /v1/orderlines, GET /v1/orderlines/<Id>, PATCH /v1/orderlines/<Id>
Scopes: orderlines:read, orderlines:write
Filters: modifiedSince, modifiedBefore, orderNr, includeZeroQuantity

FieldTypeWritable
Idtext
OrderNrtextyes
ProductNrtextyes
ProductNametext
Quantitynumberyes
Unittextyes
Pricenumberyes
PurchasePricenumberyes
PriceListNrtextyes
PurchasePriceListNrtextyes
SupplierNrtextyes
DiscountPctnumberyes
DiscountPricenumberyes
TotalPricenumber
DeliveryDatedate (ISO 8601)yes
Notetextyes
InternalNotetextyes
Custom1textyes
Custom2textyes
Custom3textyes
Modifieddate (ISO 8601)

contracts — contract

GET /v1/contracts, GET /v1/contracts/<Nr>, POST /v1/contracts, PATCH /v1/contracts/<Nr>
Scopes: contracts:read, contracts:write
Filters: modifiedSince, modifiedBefore, status, customerNr

FieldTypeWritable
Nrtextyes
Statustext
CustomerNrtextyes
CustomerNametext
CustomerReftextyes
CustomerOrderertextyes
StartDatedate (ISO 8601)yes
StopDatedate (ISO 8601)yes
LastGenerationDatedate (ISO 8601)
GeneratedStartDatedate (ISO 8601)yes
GeneratedStopDatedate (ISO 8601)yes
IsGeneratedtrue/false
Object1Nrtextyes
Object1Nametext
Object2Nrtextyes
Object2Nametext
Object3Nrtextyes
Object3Nametext
Object4Nrtextyes
Object4Nametext
Custom1textyes
Custom2textyes
Custom3textyes
Custom4textyes
Notetextyes
Modifieddate (ISO 8601)
ExtraFieldsobject, string → stringyes

resources — resource

GET /v1/resources, GET /v1/resources/<Nr>, POST /v1/resources, PATCH /v1/resources/<Nr>
Scopes: resources:read, resources:write
Filters: modifiedSince, modifiedBefore, name, category

FieldTypeWritable
Nrtextyes
Statustext
Nametextyes
Categorytextyes
ProductNrtextyes
LogUnittextyes
BasePricenumberyes
CostPricenumberyes
Custom1textyes
Custom2textyes
Custom3textyes
Notetextyes
Modifieddate (ISO 8601)
ExtraFieldsobject, string → stringyes

projects — project

GET /v1/projects, GET /v1/projects/<Nr>, POST /v1/projects, PATCH /v1/projects/<Nr>
Scopes: projects:read, projects:write
Filters: modifiedSince, modifiedBefore, customerNr, owner, status

FieldTypeWritable
Nrtextyes
Statustext
Nametextyes
CustomerNrtextyes
CustomerNametext
CustomerReftextyes
CustomerOrderertextyes
Ownertextyes
PlanStartDatedate (ISO 8601)yes
PlanStopDatedate (ISO 8601)yes
Object1Nrtextyes
Object1Nametext
Object2Nrtextyes
Object2Nametext
Object3Nrtextyes
Object3Nametext
Object4Nrtextyes
Object4Nametext
CreatedBytext
CreatedDatedate (ISO 8601)
Custom1textyes
Custom2textyes
Custom3textyes
Custom4textyes
Notetextyes
Modifieddate (ISO 8601)
ExtraFieldsobject, string → stringyes

contacts — contact

GET /v1/contacts, GET /v1/contacts/<Id>, POST /v1/contacts, PATCH /v1/contacts/<Id>
Scopes: contacts:read, contacts:write
Filters: modifiedSince, modifiedBefore, customerNr, name

FieldTypeWritable
Idtext
CustomerNrtextyes
Statustext
Nametextyes
Positiontextyes
Phonetextyes
Mobiletextyes
Faxtextyes
Emailtextyes
MostImportanttrue/falseyes
Custom1textyes
Custom2textyes
Custom3textyes
Custom4textyes
Notetextyes
Modifieddate (ISO 8601)

suppliercontacts — supplier contact

GET /v1/suppliercontacts, GET /v1/suppliercontacts/<Id>, POST /v1/suppliercontacts, PATCH /v1/suppliercontacts/<Id>
Scopes: suppliercontacts:read, suppliercontacts:write
Filters: modifiedSince, modifiedBefore, supplierNr, name

FieldTypeWritable
Idtext
SupplierNrtextyes
Statustext
Nametextyes
Positiontextyes
Phonetextyes
Mobiletextyes
Faxtextyes
Emailtextyes
MostImportanttrue/falseyes
Custom1textyes
Custom2textyes
Custom3textyes
Custom4textyes
Notetextyes
Modifieddate (ISO 8601)

addresses — address

GET /v1/addresses, GET /v1/addresses/<Id>, POST /v1/addresses, PATCH /v1/addresses/<Id>
Scopes: addresses:read, addresses:write
Filters: modifiedSince, modifiedBefore, customerNr, supplierNr, objectNr, level

FieldTypeWritable
Idtext
OwnerTypetext
OwnerNametext
CustomerNrtextyes
SupplierNrtextyes
ObjectNrtextyes
Levelnumberyes
Statustext
Nametextyes
MostImportanttrue/falseyes
AddressLine1textyes
AddressLine2textyes
PostalCodetextyes
Citytextyes
Statetextyes
Countrytextyes
Phonetextyes
Faxtextyes
Emailtextyes
Custom1textyes
Custom2textyes
Custom3textyes
Custom4textyes
Notetextyes
Modifieddate (ISO 8601)

users — user

GET /v1/users, GET /v1/users/<Sign>
Scopes: users:read, users:write
Filters: modifiedSince, modifiedBefore, name, includePassive, includeArchived

Read only. This is a setting in the product — changing it affects every user, so it belongs in Mobigo's administration.
FieldTypeWritable
Signtext
Nametext
Statustext
Phonetext
Mobiletext
Emailtext
Custom1text
Custom2text
Custom3text
Custom4text
Notetext
Modifieddate (ISO 8601)

timetypes — time type

GET /v1/timetypes, GET /v1/timetypes/<Name>
Scopes: timetypes:read, timetypes:write
Filters: modifiedSince, modifiedBefore, billable, includeArchived

Read only. This is a setting in the product — changing it affects every user, so it belongs in Mobigo's administration.
FieldTypeWritable
Nametext
Statustext
Unittext
BasePricenumber
CostPricenumber
ProductNrtext
InternalTypetrue/false
ResourceTypetrue/false
BookingTypetrue/false
Summabletrue/false
ShowInCalendartrue/false
Colortext
SpecOptionstext
DateTypetext
Custom1text
Custom2text
Custom3text
Modifieddate (ISO 8601)

pricelists — price list

GET /v1/pricelists, GET /v1/pricelists/<Nr>
Scopes: pricelists:read, pricelists:write
Filters: modifiedSince, modifiedBefore, includeArchived

Read only. This is a setting in the product — changing it affects every user, so it belongs in Mobigo's administration.
FieldTypeWritable
Nrtext
Nametext
Statustext
HasMarkupPcttrue/false
MarkupPctnumber
Notetext
Modifieddate (ISO 8601)

objects1 — object 1

GET /v1/objects1, GET /v1/objects1/<Nr>, POST /v1/objects1, PATCH /v1/objects1/<Nr>
Scopes: objects1:read, objects1:write
Filters: modifiedSince, modifiedBefore, name, category, customerNr

FieldTypeWritable
Nrtextyes
Statustext
Nametextyes
Categorytextyes
CustomerNrtextyes
Levelnumber
ParentNrtextyes
Custom1textyes
Custom2textyes
Custom3textyes
Custom4textyes
Notetextyes
Modifieddate (ISO 8601)
ExtraFieldsobject, string → stringyes

objects2 — object 2

GET /v1/objects2, GET /v1/objects2/<Nr>, POST /v1/objects2, PATCH /v1/objects2/<Nr>
Scopes: objects2:read, objects2:write
Filters: modifiedSince, modifiedBefore, name, category, customerNr

FieldTypeWritable
Nrtextyes
Statustext
Nametextyes
Categorytextyes
CustomerNrtextyes
Levelnumber
ParentNrtextyes
Custom1textyes
Custom2textyes
Custom3textyes
Custom4textyes
Notetextyes
Modifieddate (ISO 8601)
ExtraFieldsobject, string → stringyes

objects3 — object 3

GET /v1/objects3, GET /v1/objects3/<Nr>, POST /v1/objects3, PATCH /v1/objects3/<Nr>
Scopes: objects3:read, objects3:write
Filters: modifiedSince, modifiedBefore, name, category, customerNr

FieldTypeWritable
Nrtextyes
Statustext
Nametextyes
Categorytextyes
CustomerNrtextyes
Levelnumber
ParentNrtextyes
Custom1textyes
Custom2textyes
Custom3textyes
Custom4textyes
Notetextyes
Modifieddate (ISO 8601)
ExtraFieldsobject, string → stringyes

objects4 — object 4

GET /v1/objects4, GET /v1/objects4/<Nr>, POST /v1/objects4, PATCH /v1/objects4/<Nr>
Scopes: objects4:read, objects4:write
Filters: modifiedSince, modifiedBefore, name, category, customerNr

FieldTypeWritable
Nrtextyes
Statustext
Nametextyes
Categorytextyes
CustomerNrtextyes
Levelnumber
ParentNrtextyes
Custom1textyes
Custom2textyes
Custom3textyes
Custom4textyes
Notetextyes
Modifieddate (ISO 8601)
ExtraFieldsobject, string → stringyes

times — time entry

GET /v1/times, GET /v1/times/<Id>, POST /v1/times, PATCH /v1/times/<Id>
Scopes: times:read, times:write
Filters: modifiedSince, modifiedBefore, ownerNr, ownerType, userSign, invoice

New lines are created on their task with POST /v1/tasks/<nr>/times. A line without an owner never appears on the task, so that path does not exist here.
FieldTypeWritable
Idtext
OwnerNrtext
TimeTypetextyes
UserSigntextyes
Spectextyes
Hoursnumberyes
InvoiceHoursnumberyes
Pricenumberyes
DiscountPctnumberyes
Invoicetrue/falseyes
ResourceNrtextyes
CostPerUnitnumberyes
TotalCostnumberyes
Datedate (ISO 8601)yes
StopDatedate (ISO 8601)yes
Sign1Bytextyes
Sign1Datedate (ISO 8601)yes
Sign2Bytextyes
Sign2Datedate (ISO 8601)yes
Modifieddate (ISO 8601)

materials — material line

GET /v1/materials, GET /v1/materials/<Id>, PATCH /v1/materials/<Id>
Scopes: materials:read, materials:write
Filters: modifiedSince, modifiedBefore, ownerNr, ownerType, productNr, invoice

New lines are created on their task with POST /v1/tasks/<nr>/materials. A line without an owner never appears on the task, so that path does not exist here.
FieldTypeWritable
Idtext
OwnerNrtext
ProductNrtext
ProductNametext
Unittext
Notetextyes
UserSigntextyes
Quantitynumberyes
InvoiceQuantitynumberyes
Pricenumberyes
DiscountPctnumberyes
Invoicetrue/falseyes
DeliveryDatedate (ISO 8601)yes
SupplierNrtextyes
PurchasePriceListNrtextyes
PurchasePricenumberyes
CostPerUnitnumberyes
TotalCostnumberyes
Custom1textyes
Custom2textyes
Custom3textyes
Sign1Bytextyes
Sign1Datedate (ISO 8601)yes
Sign2Bytextyes
Sign2Datedate (ISO 8601)yes
Modifieddate (ISO 8601)