myWebLog API v5

New framework

Version 5 is built on a new framework. Every function of v4 is here in a new form: token, organization, users (read, create, change, delete), memberships, user groups, objects, bookings, flight logs, flight types, transactions (read, create) and the login check.

New in v5, not in v4: the organization's expiry date types (read, create, change, delete), a user's expiry dates (read, set, remove), and every member's dates in one list.

The machine-readable description of the API is openapi.yaml (OpenAPI 3.0). The same endpoints are ready to try in Postman: import postman_collection.json and put your token in the collection's variable bearerToken, or set up OAuth 2.0 as described under Authentication.

Coming from v4? Moving from v4 to v5 lists every difference, with what to change.

Address

https://api.myweblog.se/main/v5/{resource}, for example https://api.myweblog.se/main/v5/token

One item is addressed by its id in the path, for example /users/5. An address does not end with a slash: /token works, /token/ gives 404. A query parameter the endpoint does not know gives 400, so a misspelled filter is never silently ignored.

Authentication

A token is created by an administrator on the myWebLog website (Integrations, API). It can be used in two ways.

OAuth 2.0 client credentials (recommended). Exchange the token for an access token at POST /oauth/token and send the access token with every call as Authorization: Bearer [access token]. The client id is the number shown as "Client ID" next to the token on the website; the client secret is the token itself. An access token is valid for 3600 seconds ("expires_in" says exactly). When it has expired the API answers 401 with WWW-Authenticate: Bearer error="invalid_token", and the program fetches a new one. A standard OAuth 2.0 client library does all of this by itself, given the token URL, the client id and the client secret.

The token sent directly, as in v4: Authorization: Bearer [your token]. Accepted until 2027-12-31. Every reply to such a call has a Sunset header with that date and a Link header to the migration guide. From 2028-01-01 these calls are answered with 401 (code 10015).

Both ways give the same rights, the same IP address limits and the same rate limit, and both stop working at once when the token is deleted on the website. Fetching an access token counts as one call of the token, so fetch one when you have none or it is about to expire, not before every call.

In Postman: Authorization type OAuth 2.0, grant type Client Credentials, access token URL https://api.myweblog.se/main/v5/oauth/token, client ID and client secret from the website, client authentication "Send as Basic Auth header".

Sending data

POST and PATCH take a JSON object as the request body. The one exception is POST /oauth/token, which takes form data as the OAuth 2.0 standard says. The id of the item is never in the body: it is in the address, for example PATCH /users/5.

A value in the body has the same type as in a reply. Text is text: a member number, a phone number and an account number are sent as "007", not as 7. An id is a number, yes or no is true or false, an amount is a number, and a date is written "2026-09-30". A value of another type gives 400.

A field the endpoint does not know gives 400, so a misspelled field is never silently ignored. A field inside an object is named with the object's name in front in an error reply, for example "address.city", and an item of a list by its place, counted from 0: "email[1]" is the second address.

Headers

Header In Description
Authorization Request Required: Bearer [access token] (OAuth 2.0, see Authentication), or Bearer [your token] until 2027-12-31. The word Bearer must be included. For POST /oauth/token: Basic [client_id:client_secret in base64].
WWW-Authenticate Reply With 401: Bearer, and error="invalid_token" when an access token has expired or is not accepted (fetch a new one).
Sunset
Link
Reply With a call where the token was sent directly: the date after which that stops working (2027-12-31), and a link to the migration guide.
Request-Id Request and reply Optional. Your own id for the request, at most 64 characters. It is returned with the reply, and in the body of an error reply. If you send none, one is made for you. Quote it when you contact support.
RateLimit-Limit
RateLimit-Remaining
RateLimit-Reset
Reply Calls allowed per minute for the token, calls left in the current minute, and seconds until the count starts over.
Retry-After Reply With 429: seconds to wait before the next call.
Allow Reply With 405: the methods the address accepts.
Location Reply With 201: the address of the item that was created.

Replies

A successful reply has the result under "data". When an item is created (201) or changed (200), the reply is the item, as GET gives it. When an item is deleted (204), the reply has no body.

{
  "data": {
    "description": "Postman",
    "owner": { "id": 17, "name": "Anna Berg" },
    "organization": { "id": 3, "name": "Svanshalls FK" },
    "rights": { "users": "rw", "transactions": "r" }
  }
}

An error reply follows the standard "Problem Details" (RFC 9457) and has the content type application/problem+json. "code" is the number in the error code table at the end of this page. "errors" is only present when the error is about named parameters or fields: the ones that are wrong (400), or the ones whose value is already in use (409). It names each of them, so everything can be corrected at once:

{
  "type": "https://api.myweblog.se/errors/validation",
  "title": "The request has invalid parameters",
  "status": 400,
  "code": 10005,
  "request_id": "b7e2c41a9d0f3e55",
  "errors": [
    { "field": "verbose", "code": 10007, "message": "Unknown parameter" }
  ]
}

Lists

A list has its items under "data", and says where the page is in the list:

{
  "data": [ { "id": 5, ... }, { "id": 6, ... } ],
  "meta": { "count": 2, "limit": 2, "offset": 0, "has_more": true },
  "links": { "next": "https://api.myweblog.se/main/v5/users?limit=2&offset=2" }
}
Parameter Description
limit (int)How many items to return, 1 to 500. Default 100.
offset (int)How many items to skip. Default 0. To read a whole list, follow "links.next" until it is null.
include_total (bool)true adds "meta.total", the number of all items that match the filters. It costs an extra database query, so ask for it on the first page only.
include (list)Optional blocks of each item, separated by commas, for example include=email,phone. Each block adds one key with the same name. Only the blocks asked for are fetched.
exclude (list)Fields to leave out of each item, separated by commas, for example exclude=comment,type_name. It makes the reply smaller. Only the fields an item always has can be named; a block is left out by not asking for it.

include and exclude also work where one item is returned, and on the reply to a POST or PATCH. exclude takes the names at the top level of an item: exclude=membership leaves out the whole membership of a user, and a field inside it cannot be left out on its own. A name the item does not have gives 400, and the reply lists the names that can be used. A field that is left out can still be used as a filter.

Filters: a yes/no filter (bool) takes true or false; leave it out for both. In a text search (search), * stands for any characters and is the only wildcard. A date is written YYYY-MM-DD. A value that is missing is null, and a list without items is [].

Types: an id is a number. A date is written "2026-10-03". A point in time is in UTC and written "2026-10-03T23:10:00Z"; its name ends with _at. What a person types, such as a member number, a phone number or an account number, is always text.

Status codes

Status Meaning
200OK.
201Created. The reply is the new item, and the Location header has its address.
204Deleted. The reply has no body.
400A parameter or the body is wrong: a value is missing or has the wrong form, a parameter or field is not known, or a PATCH has nothing to change. The body of a POST or PATCH must be a JSON object.
401The token or access token is missing or not accepted, or the access token has expired (then WWW-Authenticate has error="invalid_token": fetch a new one). 401 always and only concerns the token.
403The token is valid but lacks the right for this endpoint.
404The address or the item does not exist.
405The address exists but not with this method. See the Allow header.
409The request is right in its form but cannot be carried out as things are: a username or member number is already in use, the same transaction has been sent before, the date is before the transaction lock date, the user has a balance or is an administrator, the organization does not use the flight log, or other data depends on a setting that is being deleted (see below).
429Too many calls: more than 120 per minute with one token, or too many failed token checks from one IP address. See the Retry-After header.
500A fault on our side. It has been logged; quote the request id to support.

Deleting a setting that data depends on

Some of the organization's settings have members' data that depends on them. An expiry date type has the members' dates of it. Deleting such a setting deletes that data too, and it cannot be brought back, the same as on the website.

So the API asks first. DELETE of a setting that data depends on answers 409 with code 10106 and deletes nothing. "dependents" says what depends on it and how much. The same call with delete_dependents=true deletes the setting together with that data. A setting that nothing depends on is deleted at once (204).

{
  "type": "https://api.myweblog.se/errors/conflict",
  "title": "Other data depends on this",
  "status": 409,
  "code": 10106,
  "request_id": "b7e2c41a9d0f3e55",
  "detail": "Members have dates of this type. Send delete_dependents=true to delete them together with it.",
  "dependents": [
    { "name": "user_expiry_dates", "count": 37 }
  ]
}

A program can send the DELETE without the parameter first, to see what would be deleted. The parameter is called delete_dependents wherever it is used.

Endpoints

Click on an endpoint to see its details. An endpoint shown in gray is "To be implemented": it is planned, as in v4, and cannot be called yet. Its address can change before it is built.

token

POST
/oauth/token Exchanges a token for an access token that is valid for one hour (OAuth 2.0 client credentials).
Information Body Response

Called without a Bearer token. The client id and secret are sent in a Basic Authorization header (client_id:client_secret in base64, which is what OAuth 2.0 libraries do), or as the form fields client_id and client_secret.

The client id is the token's "Client ID" on the website; the client secret is the token. The access token has the token's rights, is limited to the same IP addresses, counts against the same rate limit and stops working when the token is deleted. Fetching it counts as one call.

The reply follows the OAuth 2.0 standard: "access_token", "token_type" ("Bearer"), "expires_in" (3600) and, for information, "scope" (the rights, as "users:rw transactions:r"). An error is {"error": ..., "error_description": ...} with 400 (invalid_request, unsupported_grant_type) or 401 (invalid_client: wrong id or secret, deleted token, IP address not allowed, or the owner is no longer an administrator). 429 and 500 are the API's usual replies.

Form data (application/x-www-form-urlencoded), not JSON:

grant_type: must be client_credentials.
client_id, client_secret: only when they are not sent in the Basic Authorization header.

200 OK
GET
/token The token used for the request: its description, owner, organization and rights. Also the way to test that a token works.
Information Parameters Response

Any valid token can call this. It replaces /token/rights/ and /test/ in v4.

"rights" has one entry per module: "r" (read), "w" (write) or "rw". In v5 a module has the same name as its resource, so a token's rights for Economy, Logbook, Login check and Organisation are listed as transactions, flightlogs, login_checks and organization.

None
200 OK

users

GET
/users The members of the organization, sorted by name.
Information Filters Blocks (include) Response

Requires read access in the "Users" module.

Always returned: id, username, member_number, ksak_member_number, nlf_member_number, name, first_name, last_name_particle, last_name, last_first_name, membership (id and name), active, locked, locked_login, has_invalid_email_address.

member_number, phone numbers and postal codes are always text, so 007 stays "007".

The last four blocks hold the most personal data and also require read access in the "Users, sensitive data" module (users_sensitive).

The block expiry_dates has the user's dates: id and name of the expiry date type (see expiry dates below), expiry_date, and expired (the date has passed). "Never expires" is written as the date 2999-12-31. A date is set with PUT /users/{id}/expiry-dates/{type_id} and removed with DELETE.

Replaces GET /users/ in v4. "verbose" is replaced by the blocks.

name (search)
username (search)
member_number (search)
membership_id (int)
membership_name (search)
active (bool)
locked (bool)
locked_login (bool)
has_invalid_email_address (bool)

limit, offset, include_total, exclude
email
address
phone
user_groups
roles
expiry_dates
account
misc_info
personal_identification_number
internal_notes
emergency_contact
access_card
200 OK
GET
/users/{id} One user.
Information Parameters Response

Requires read access in the "Users" module. The same fields and blocks as in the list.

Returns the user as one object under "data", or 404 if the organization has no user with that id.

Replaces GET /users/?id= in v4.

include, exclude
200 OK
POST
/users Creates a user.
Information Body Response

Requires write access in the "Users" module.

Creates the login and the membership together. No password is set: the new user sets one with "Reset my password" on the login page.

The username must start with the organization's id and a dash, for example "3-nils" in organization 3. Without a member_number the user gets the highest member number in the organization plus one, or 10000 if no user in the organization has a number yet.

membership_id and user_groups are ids of the organization's memberships and user groups. email and street_address are lists with at most two items. The country code of the mobile number may be written 46, +46 or 0046.

The reply is the new user, as GET /users/{id} gives it, and takes the same include and exclude parameters. The Location header has the user's address.

409 with code 10030 if the username, the member number or the KSAK member number is already in use; "errors" names the fields. 409 with code 10040 if a trial organization already has 5 active users.

Replaces POST /users/ in v4. The field names are the same. New: every value must have the type shown here, and email, street_address and user_groups must be lists.

{
  "username": text*,
  "member_number": text,
  "ksak_member_number": text (digits),
  "external_customer_number": text,
  "first_name": text*,
  "last_name_particle": text,
  "last_name": text*,
  "personal_identification_number": text,
  "membership_id": number*,
  "user_groups": [number, ...],
  "email": [text, text],
  "address": {
    "street_address": [text, text],
    "postal_code": text,
    "city": text,
    "country": text
  },
  "phone": {
    "home": text,
    "work": text,
    "mobile": {
      "country_code": text (digits),
      "initial_number": text (digits),
      "number": text (digits)
    }
  },
  "emergency_contact": {
    "name": text,
    "phone": text,
    "relation": text
  }
}

* required

Parameters: include, exclude
201 Created
PATCH
/users/{id} Changes a user.
Information Body Response

Requires write access in the "Users" module.

Only the fields that are sent are changed. To change only the access card number, send {"access_card": {"number": "987654321"}}.

A field sent as null or as an empty text is emptied. first_name and last_name cannot be emptied. A list replaces the whole list: to keep two email addresses, send both. An email address that changes is no longer marked as invalid.

"active": false makes the user inactive, true makes the user active again.

Name, address, email and phone belong to the user's login, and can only be changed for users whose login was created by this organization (the username starts with the organization's id). For other users they give 409 with code 10105.

The reply is the changed user, as GET /users/{id} gives it, and takes the same include and exclude parameters.

400 with code 10105 if the body has no fields. 404 if the organization has no user with that id. 409 with code 10040 if a user is made active in a trial organization that already has 5 active users.

Replaces PATCH /users/ in v4. Changed: the id is in the address and not in the body; "status": {"active"} is now "active"; email is a list of addresses, not of objects; a date is written YYYY-MM-DD.

{
  "first_name": text,
  "last_name_particle": text,
  "last_name": text,
  "active": true or false,
  "email": [text, text],
  "address": {
    "street_address": [text, text],
    "postal_code": text,
    "city": text
  },
  "phone": {
    "home": text,
    "work": text,
    "mobile": {
      "country_code": text (digits),
      "initial_number": text (digits),
      "number": text (digits)
    }
  },
  "misc_info": text,
  "internal_notes": text,
  "access_card": {
    "number": text (at most 50 characters),
    "code": text (at most 10 characters),
    "expiry_date": date
  }
}

Parameters: include, exclude
200 OK
DELETE
/users/{id} Deletes a user.
Information Parameters Response

Requires write access in the "Users" module.

WARNING: a deleted user cannot be restored. Consider making the user inactive instead: PATCH /users/{id} with {"active": false}.

An administrator cannot be deleted, and neither can a user whose account balance is not zero: 409 with code 10105.

The reply has no body. 404 if the organization has no user with that id.

Replaces DELETE /users/?id= in v4.

None
204 No Content

organization

GET
/organization The token's own organization.
Information Parameters Response

Any valid token can call this.

Returns id, name and location: reference_icao, latitude, longitude, country and timezone.

Replaces GET /organizations/location/ in v4.

None
200 OK

memberships

GET
/memberships The organization's membership types, sorted by name.
Information Filters Blocks (include) Response

Requires read access in the "Users" module.

Returns id and name. The block users adds the users who have the membership: id, name, member_number and active.

Replaces GET /users/memberships/ in v4.

name (search)

limit, offset, include_total, exclude
users
200 OK
GET
/memberships/{id} One membership.
Information Parameters Response

Requires read access in the "Users" module. The same fields and block as in the list.

404 if the organization has no membership with that id.

include, exclude
200 OK

user groups

GET
/user-groups The organization's own groups of users, sorted by name.
Information Filters Blocks (include) Response

Requires read access in the "Users" module.

Returns id and name. The block users adds the users in the group: id, name, member_number and active.

Replaces GET /users/groups/ in v4.

name (search)

limit, offset, include_total, exclude
users
200 OK
GET
/user-groups/{id} One user group.
Information Parameters Response

Requires read access in the "Users" module. The same fields and block as in the list.

404 if the organization has no user group with that id.

include, exclude
200 OK

expiry dates

GET
/expiry-date-types The organization's types of expiry date, sorted by name.
Information Filters Response

Requires read access in the "Organization settings" module (organization_settings).

An expiry date type is a date the organization keeps for its members, such as a medical or a licence. On the website they are set up under Settings, Expiry dates. A member's date of a type is in the block expiry_dates of a user, where id is the type's id.

Returns id, name, may_never_expire, members_can_change, image_required, booking (required, warning_only, also_for_students, excluded_object_ids) and reminders (at_login, days_before). See POST below for what each means.

New in v5.

name (search)

limit, offset, include_total, exclude
200 OK
GET
/expiry-date-types/{id} One expiry date type.
Information Parameters Response

Requires read access in the "Organization settings" module. The same fields as in the list.

404 if the organization has no expiry date type with that id.

exclude
200 OK
POST
/expiry-date-types Creates an expiry date type.
Information Body Response

Requires write access in the "Organization settings" module.

Only name is required. Two types of the organization cannot have the same name, in any upper or lower case: 409 with code 10030. At most 30 characters of the name are stored.

A field that is left out gets what the website's empty form gives: false, no excluded objects, and reminders 30 days before the date.

may_never_expire: a member's date may be "never expires", written as the date 2999-12-31. members_can_change: members may change their own date on the website. image_required: a date counts as expired until an administrator has added an image (scanned copy) of it on the member card.

booking.required: the date is checked when a member books; warning_only: an expired date warns instead of stopping the booking; also_for_students: the student on a booking with an instructor is checked too; excluded_object_ids: the objects (ids from GET /objects) the date is not checked for, none for every object.

reminders.at_login: the member is reminded after logging in when the date is near or has passed; days_before: how many days before the date the reminders start, 0 to 365.

The reply is the new type, with its address in the Location header.

{
  "name": text (required),
  "may_never_expire": true or false,
  "members_can_change": true or false,
  "image_required": true or false,
  "booking": {
    "required": true or false,
    "warning_only": true or false,
    "also_for_students": true or false,
    "excluded_object_ids": [int, int]
  },
  "reminders": {
    "at_login": true or false,
    "days_before": int (0 to 365)
  }
}

Parameters: exclude
201 Created
PATCH
/expiry-date-types/{id} Changes an expiry date type.
Information Body Response

Requires write access in the "Organization settings" module.

Only the fields that are sent are changed. None of them can be null. excluded_object_ids replaces the whole list. The name cannot be changed, as on the website.

Turning image_required on makes every member who has a date of the type but no image count as expired at once, as on the website. A member's 2999-12-31 date stays when may_never_expire is turned off.

The reply is the changed type. 400 with code 10105 if the body has no fields. 404 if the organization has no expiry date type with that id.

The fields of POST, without name.

Parameters: exclude
200 OK
DELETE
/expiry-date-types/{id} Deletes an expiry date type.
Information Parameters Response

Requires write access in the "Organization settings" module.

WARNING: the members' dates of the type, and their images, are deleted with it and cannot be restored.

While members have dates of the type, it is only deleted with delete_dependents=true. Without it the reply is 409 with code 10106, which says how many dates there are, and nothing is deleted. See Deleting a setting that data depends on.

The reply has no body. 404 if the organization has no expiry date type with that id.

delete_dependents (bool)
204 No Content
GET
/expiry-dates Every member's expiry dates, the earliest first.
Information Filters Response

Requires read access in the "Users" module.

The dates of the organization's members in one list, one item per date, for reports and reminders. Sorted by the date, the earliest first, then by the member's name. Only dates that exist are listed: a member who has no date of a type is not (GET /users?include=expiry_dates shows that).

Returns user (id and name), type (id and name of the expiry date type), expiry_date (2999-12-31 means "never expires"), expired (the date is before today), image_required and has_image.

Example: the dates that run out in November 2026: ?expiry_date_from=2026-11-01&expiry_date_to=2026-11-30. The medicals that have passed: ?type_id=12&expired=true.

type_id (int)
user_id (int)
active (bool)
expired (bool)
expiry_date_from (date)
expiry_date_to (date)

limit, offset, include_total, exclude
200 OK
GET
/users/{id}/expiry-dates A user's expiry dates.
Information Parameters Response

Requires read access in the "Users" module.

Every expiry date type of the organization, with the user's date of it, as the member card shows them. Sorted by the name of the type.

Returns id and name of the type, expiry_date (null when the user has no date of it; 2999-12-31 means "never expires"), expired (the date is before today; null without a date), image_required and has_image. On the website a date whose type requires an image counts as expired until it has one; expired here is the date alone.

404 if the organization has no user with that id.

limit, offset, include_total, exclude
200 OK
PUT
/users/{id}/expiry-dates/{type_id} Sets a user's expiry date.
Information Body Response

Requires write access in the "Users" module.

Creates the user's date of the type, or changes it. The same as saving the date on the member card: a changed date gets its reminder emails again, and its image (scanned copy) is removed, since it proved the old date. The same date again saves nothing.

2999-12-31 means "never expires" and is only accepted when the type has may_never_expire. A date that has passed is accepted. Whether members may change the date themselves (members_can_change) does not apply here.

The reply is the user's date of the type, as GET gives it: 200 also when the date is new. To remove a date, use DELETE; null gives 400.

404 if the organization has no user or no expiry date type with that id.

{
  "expiry_date": date (required)
}

Parameters: exclude
200 OK
DELETE
/users/{id}/expiry-dates/{type_id} Removes a user's expiry date.
Information Parameters Response

Requires write access in the "Users" module.

Removes the user's date of the type, and its image, as emptying the date on the member card does.

The reply has no body. 404 if the organization has no user or no expiry date type with that id, or the user has no date of the type.

None
204 No Content

objects

GET
/objects What can be booked: the organization's own objects and the ones shared with it.
Information Filters Blocks (include) Response

Requires read access in the "Objects" module. Sorted by category, then registration.

Always returned: id, registration, category (id and name: 0 = Aircraft, 1 = Equipment, 2 = Premises), type_designator and type_name (aircraft only, otherwise null), comment, organization (id and name of the owner) and active.

status: total_id, maintenance_id and remarks_id (0 = OK, 1 = yellow, 2 = red) and remaining_time. settings: aircraft only, otherwise null.

status, active_remarks and time_summary are worked out the same way as on the website and take the longest to fetch, so ask only for the blocks you need.

Replaces GET /objects/ and GET /objects/status/ in v4 (use include=status for the latter).

registration (search)
type_designator (search)
category_id (int)
organization_id (int)
organization_name (search)
active (bool)

limit, offset, include_total, exclude
status
active_remarks
maintenance_items
settings
time_summary
200 OK
GET
/objects/{id} One object.
Information Parameters Response

Requires read access in the "Objects" module. The same fields and blocks as in the list.

404 if the organization neither has nor is shared an object with that id.

include, exclude
200 OK

bookings

GET
/bookings Bookings of the organization's own objects and of the objects shared with it, sorted by start time.
Information Filters Response

Requires read access in the "Bookings" module.

Returned: id, start_at and end_at (UTC), start_local and end_local (the time at the object's organization, with its offset from UTC), timezone, object (id, registration, type_designator, category, organization), user (the booker: id, name, email, mobile), student (the same, or null), type, is_primary, expected_airborne and comment.

The email and mobile number follow each member's own settings, the same as in the booking calendar. A value the member has chosen not to show is null.

date_from and date_to are local dates: a booking is returned if it ends on or after date_from and starts on or before date_to.

Replaces GET /bookings/ in v4.

date_from (date)
date_to (date)
object_id (int)
object_registration (search)
object_organization_id (int)
object_category_id (int)
user_id (int)
user_name (search)
student_id (int)
student_name (search)
is_primary (bool)

limit, offset, include_total, exclude
200 OK
GET
/bookings/{id} One booking.
Information Parameters Response

Requires read access in the "Bookings" module. The same fields as in the list.

404 if there is no such booking of the organization's objects.

exclude
200 OK
GET
/bookings/instructors To be implemented
GET
/bookings/students To be implemented
POST
/bookings To be implemented
PATCH
/bookings/{id} To be implemented
DELETE
/bookings/{id} To be implemented

flightlogs

GET
/flightlogs The flights logged on the organization's own objects, oldest first.
Information Filters Response

Requires read access in the "Logbook" module (flightlogs).

Returned: id, date, object, flight_type, departure (airport, blockoff_at, takeoff_at, tachometer, hobbs), via_route, arrival (airport, landing_at, blockon_at, tachometer, hobbs), crew_hidden, pilot, instructor and scout (each with id, name, member_number and organization, or null), is_solo_instruction, uplift (fuel, oil), totals (block, airborne, tach and hobbs, each in hours and in minutes), number_of_flights, daily_check, comment, transaction_id, remark_id and created_at.

All times are UTC. When a flight passes midnight, the later times are on the day after the date.

is_solo_instruction is true for a student's solo flight, false for a flight that is not one, and null when it was not filled in. (v4 gave the stored codes "EK" and "DK" in instruction.is_solo.)

When the object's organization hides the pilot in the log (integrity setting), crew_hidden is true and pilot, instructor and scout are null. The pilot, instructor and scout filters never match such flights, and has_instructor treats them as flights without an instructor.

Replaces GET /flightlogs/ in v4.

date_from (date)
date_to (date)
object_id (int)
object_registration (search)
object_organization_id (int)
object_organization_name (search)
departure_icao (search)
arrival_icao (search)
pilot_id (int)
pilot_name (search)
pilot_member_number (search)
instructor_id (int)
instructor_name (search)
instructor_member_number (search)
has_instructor (bool)
scout_id (int)
scout_name (search)
scout_member_number (search)
has_remark (bool)
remark_id (int)

limit, offset, include_total, exclude
200 OK
GET
/flightlogs/{id} One logged flight.
Information Parameters Response

Requires read access in the "Logbook" module (flightlogs). The same fields as in the list.

404 if there is no such flight on the organization's objects.

exclude
200 OK
POST
/flightlogs To be implemented
GET
/flight-types The organization's flight types, sorted by name.
Information Parameters Response

Requires read access in the "Logbook" module (flightlogs).

Returns id and name.

409 (code 10200) if the organization does not use the flight log.

Replaces GET /flightlogs/flighttypes/ in v4.

limit, offset, include_total, exclude
200 OK
GET
/users/{id}/approved-flight-types The flight types a user may log, per object.
Information Parameters Response

Requires read access in the "Logbook" module (flightlogs).

Returns the objects the user may log flights on. Each item has object (id, registration) and flight_types (a list of id and name). The rules are the same as when the member logs a flight on the website.

404 if the organization has no user with that id. 409 (code 10200) if the organization does not use the flight log.

Replaces GET /flightlogs/flighttypes/approved/?user_id= in v4.

limit, offset, include_total, exclude
200 OK

remarks

GET
/remarks To be implemented
POST
/remarks To be implemented

transactions

GET
/transactions What has been charged to and paid into the members' accounts, newest first.
Information Filters Blocks (include) Response

Requires read access in the "Economy" module (transactions).

Returned: id, date, user (id, name, external_customer_number), flightlog_id, amount, comment and created_at (UTC). A negative amount is a charge, a positive amount a payment.

The blocks show how the transaction is booked. account_rows has one row per part of the transaction: account, description, debit, credit, cost_center, project. account_summary has one sum per account, cost center and project. An account number is always text.

Replaces GET /transactions/ in v4. include_account_rows and include_account_summary are replaced by the blocks.

user_id (int)
user_name (search)
date (date)
date_from (date)
date_to (date)
amount (decimal)
comment (search)
external_customer_number (search)

limit, offset, include_total, exclude
account_rows
account_summary
200 OK
GET
/transactions/{id} One transaction.
Information Parameters Response

Requires read access in the "Economy" module (transactions). The same fields and blocks as in the list.

404 if the organization has no transaction with that id.

include, exclude
200 OK
GET
/transactions/swish To be implemented
POST
/transactions Creates a transaction on a member's account.
Information Body Response

Requires write access in the "Economy" module (transactions).

"amount" is the change of the member's account: negative for a charge, positive for a payment. It is a number with at most two decimals, not 0, from -100000 to 100000.

"date" must not be in the future, and not before the organization's transaction lock date.

"accounting" is optional and books the transaction on one debit account and one credit account, with a cost center and a project. They must be among the organization's own. An account number is text. "accounting.amount" can repeat the amount without its sign, as a check.

The reply is the new transaction, as GET /transactions/{id} gives it, and takes the same include and exclude parameters. The Location header has its address.

409 with code 10150 if the date is before the lock date. 409 with code 10130 if the user already has a transaction with the same date, amount and comment: it is taken for the same transaction sent twice.

Replaces POST /transactions/ in v4. Changed: "accounting.debit" and "accounting.credit" are now "debit_account" and "credit_account", and are text; the amount must be a number; a date is written YYYY-MM-DD; the reply is 201 with one transaction.

{
  "user_id": number*,
  "date": date*,
  "amount": number*,
  "comment": text*,
  "accounting": {
    "amount": number,
    "debit_account": text (digits),
    "credit_account": text (digits),
    "cost_center": text,
    "project": text
  }
}

* required

Parameters: include, exclude
201 Created
PATCH
/transactions/{id} To be implemented

login checks

POST
/login-checks Checks a member's username and password.
Information Body Response

Requires read access in the "Login check" module (login_checks).

A simple login check for an organization's own website. It tells whether the username and the password are the login of an active member of the organization.

The reply is 200 both when the login is right and when it is wrong. "valid" says which: {"valid": true, "user": {...}} with the member's id, username, name, first_name, last_name_particle and last_name, or {"valid": false, "user": null}. 401 only ever concerns the token.

The check counts in the website's own limit on failed logins. After too many wrong passwords for a username the reply is 429 with code 10012, and the Retry-After header says how many seconds to wait.

Replaces POST /logincheck/ in v4, which answered 401 for a wrong login.

{
  "username": text*,
  "password": text*
}

* required
200 OK

Examples

GET objects request

https://api.myweblog.se/main/v5/objects?registration=SE-ABC&include=status

GET objects reply

{
  "data": [
    {
      "id": 24,
      "registration": "SE-ABC",
      "category": { "id": 0, "name": "Aircraft" },
      "type_designator": "C172",
      "type_name": "CESSNA 172",
      "comment": "Ã…rsmodell 2002",
      "organization": { "id": 3, "name": "Svanshalls FK" },
      "active": true,
      "status": {
        "total_id": 1,
        "maintenance_id": 1,
        "remarks_id": 0,
        "remaining_time": {
          "soft_airborne": 12.5,
          "soft_airborne_formatted": "12:30",
          "hard_airborne": 22.5,
          "hard_airborne_formatted": "22:30"
        }
      }
    }
  ],
  "meta": { "count": 1, "limit": 100, "offset": 0, "has_more": false },
  "links": { "next": null }
}

Linux/Mac cURL Requests

## Fetch an access token (OAuth 2.0). {client_id} is the token's Client ID on the website, {my_auth_token} is the token.
curl -u "{client_id}:{my_auth_token}" \
  -d "grant_type=client_credentials" \
  "https://api.myweblog.se/main/v5/oauth/token"
## {"access_token":"mwlat_...","token_type":"Bearer","expires_in":3600,"scope":"users:rw transactions:r"}

## Check that the access token works. Until 2027-12-31 the token itself can be sent instead.
curl "https://api.myweblog.se/main/v5/token" \
  -H "Authorization: Bearer {access_token}"

## Fetch the active users, with their email addresses and phone numbers
curl "https://api.myweblog.se/main/v5/users?active=true&include=email,phone" \
  -H "Authorization: Bearer {access_token}" \
  -H "Request-Id: {my_request_id}"

## Fetch the transactions of January 2026, with how they are booked. Follow "links.next" for the next page.
curl "https://api.myweblog.se/main/v5/transactions?date_from=2026-01-01&date_to=2026-01-31&include=account_rows" \
  -H "Authorization: Bearer {access_token}" \
  -H "Request-Id: {my_request_id}"

## Fetch the flights of January 2026, without the uplift and the totals
curl "https://api.myweblog.se/main/v5/flightlogs?date_from=2026-01-01&date_to=2026-01-31&exclude=uplift,totals" \
  -H "Authorization: Bearer {access_token}" \
  -H "Request-Id: {my_request_id}"

## Charge a member's account with 500.00, booked on two accounts. The reply is 201 with the new transaction.
curl -X POST "https://api.myweblog.se/main/v5/transactions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer {access_token}" \
  -H "Request-Id: {my_request_id}" \
  -d '{
    "user_id": 987654321,
    "date": "2026-01-29",
    "amount": -500.00,
    "comment": "Test transaction via cURL",
    "accounting": {
      "amount": 500.00,
      "debit_account": "3011",
      "credit_account": "1910"
    }
  }'

## Make a user inactive and change the access card number
curl -X PATCH "https://api.myweblog.se/main/v5/users/987654321" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer {access_token}" \
  -d '{"active": false, "access_card": {"number": "987654321"}}'

Reply when a transaction is sent twice

{
  "type": "https://api.myweblog.se/errors/conflict",
  "title": "Duplicate transaction: the user already has one with this date, amount and comment",
  "status": 409,
  "code": 10130,
  "request_id": "b7e2c41a9d0f3e55"
}

Reply when fields of a body are wrong

{
  "type": "https://api.myweblog.se/errors/validation",
  "title": "The request has invalid fields",
  "status": 400,
  "code": 10006,
  "request_id": "b7e2c41a9d0f3e55",
  "errors": [
    { "field": "membership_id", "code": 10006, "message": "Unknown membership" },
    { "field": "email[1]", "code": 10006, "message": "Not a valid email address" },
    { "field": "phone.mobile.number", "code": 10006, "message": "Must be text with digits only" }
  ]
}

PHP cURL Request

// The bookings of one object in October 2026
$address = 'https://api.myweblog.se/main/v5/bookings?object_id=24&date_from=2026-10-01&date_to=2026-10-31';

$headers = [
  'Authorization: Bearer {access_token}',
  'Request-Id: {my_request_id}'
];

$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $address);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);

$response = curl_exec($curl);
$response_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);

// 200: the bookings are in "data". Anything else: the reason is in "title".
$reply = json_decode($response, true);
var_dump($response_code);
var_dump($reply);

Error Codes

"code" in an error reply is one of these numbers. They are the same numbers as in v4 wherever v4 had the error. When "errors" names parameters or fields, each of them has a code of its own.

Code Name Comment Response
10000 HTTPS is required A call over plain HTTP is normally redirected to HTTPS by the server before it reaches the API.
403 Forbidden
10001 Unauthorized Check that your token or access token is correct and valid, and that it is sent as "Authorization: Bearer [token]". An access token that has expired gives this too, with error="invalid_token" in the WWW-Authenticate header: fetch a new one.
401 Unauthorized
10002 No access to the module The token lacks the read or write access the endpoint needs. Check the module access settings of the token.
403 Forbidden
10004 The request body must be a JSON object The body of a POST or PATCH could not be read as JSON.
400 Bad Request
10005 Invalid parameter A query parameter has the wrong form. "errors" names it.
400 Bad Request
10006 Invalid field A field of the body is missing, has the wrong type or form, or refers to something the organization does not have (a membership, a user group). "errors" names each field.
400 Bad Request
10007 Unknown parameter or field The endpoint does not have the named query parameter or body field. Check the spelling.
400 Bad Request
10010 Internal error A database error on our side. It has been logged. Nothing was saved.
500 Internal Server Error
10012 Too many failed login attempts Login check: the username has had too many wrong passwords. Wait the number of seconds in the Retry-After header.
429 Too Many Requests
10013 Too many requests More than 120 calls in a minute with the same token. Wait the number of seconds in the Retry-After header.
429 Too Many Requests
10014 Too many failed authorization attempts Too many calls with a token that was not accepted have come from your IP address. Wait the number of seconds in the Retry-After header.
429 Too Many Requests
10015 The token must be exchanged for an access token From 2028-01-01 a token can no longer be sent directly. Fetch an access token at POST /oauth/token (see Authentication).
401 Unauthorized
10020 Internal error A fault on our side. It has been logged; quote the request id to support if it persists.
500 Internal Server Error
10030 Duplicate detected A username, member number or KSAK member number, or the name of an expiry date type, is already in use. "errors" names the fields. Usernames and the names of expiry date types are compared without regard to upper and lower case.
409 Conflict
10040 User limit reached A trial organization can have at most 5 active users. Contact support to activate the organization.
409 Conflict
10050 Method not allowed The address exists, but not with this method. The Allow header lists the methods it has.
405 Method Not Allowed
10100 Not found The address does not exist, or the organization has no item with that id (404). As the code of a field in "errors": the user the body refers to does not exist (400).
404 Not Found
10105 Not modified The change or the deletion cannot be made. 400: the body of a PATCH has no fields. 409: an administrator or a user with a balance cannot be deleted; name, address, email and phone cannot be changed for a user whose login was created elsewhere.
400 Bad Request
409 Conflict
10106 Other data depends on this A setting that members' data depends on was not deleted. "dependents" says what and how much. Send the same DELETE with delete_dependents=true to delete it together with that data. See "Deleting a setting that data depends on".
409 Conflict
10110 Invalid date A date must be written YYYY-MM-DD and exist. "errors" names the parameter or field.
400 Bad Request
10121 Invalid amount The amount of a transaction must not be 0 and must be from -100000 to 100000. "accounting.amount" must be the same amount without its sign.
400 Bad Request
10130 Duplicate transaction The user already has a transaction with the same date, amount and comment.
409 Conflict
10150 Before the transaction lock date A transaction cannot be created with a date before the organization's transaction lock date.
409 Conflict
10151 Date in the future A transaction cannot have a date in the future.
400 Bad Request
10162 Debit account not found The account is not among the organization's accounts.
400 Bad Request
10163 Credit account not found The account is not among the organization's accounts.
400 Bad Request
10164 Cost center not found The cost center is not among the organization's cost centers.
400 Bad Request
10165 Project not found The project is not among the organization's projects.
400 Bad Request
10190 Unable to save the transaction Contact support.
500 Internal Server Error
10200 The organization does not use the flight log Flight types were asked for, but the flight log is switched off for the organization.
409 Conflict

Codes of v4 that are no longer sent: 10003; 10120 (amount format), 10140 (comment empty) and 10160 (account format), which are now 10006 with the field named in "errors".