Zamar Backend

This is a specification about the REST API used in Zamar.

It is highly recommended that you get the tool PostMan as it will greatly help with the introduction to this REST API specification.

The first thing you will need to do is to create an account. That can be done with the [signup] API or in the frontend GUI.

Then you must [login] with the REST API

Author

Author-get

To retrieve a list of available authors, you must in the database you must GET the following body to the BACKENDURL/author/get

No Authorization

{
    "how-many-per-page": 20,
    "page-number": 0,
    "filters":[{"field":"name", "like":"John"},{"field":"status", "like":"Submitted"}]
    "sort-by":{"field":"name", "direction":"ASC"}
}

Here is a typical result of such a call:

{
    "status": 200,
    "total-count": 1,
    "page": 0,
    "data": [
        {
            "id": 8,
            "name": "John",
            "type": "Artist",
            "status": "Submitted",
            "firstname": "Thomas",
            "created": "2023-03-14 13:49:48"
        }
    ]
}

Note that “total-count” is not necessarily the number of records returned but it is the total number of results found. With this number you can count the total number of pages in the result = “total-count” divided by “how-many-per-page”. The actual number returned is limited by the call field “how-many-per-page”

Here is the json schema that defines the body of this REST API call.

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "how-many-per-page": {
      "type": "integer"
    },    
    "page-number": {
      "type": "integer"
    },
    "filters": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "field": {
            "type": "string",
            "enum": ["id","name","type","created","status","firstname"]
          },
          "like": {
            "type": "string"
          }
        },
        "required": [
          "field",
          "like"
        ]    
      },
      "minItems": 1,
      "uniqueItems": true       
    },
    "sort-by": {
      "type": "object",
      "properties": {
        "field": {
          "type": "string",
          "enum": ["id","name","type","created","status","firstname"]   
        },
        "direction": {
          "type": "string",
          "enum": [
            "ASC",
            "DESC"
          ]          
        }
      },
      "required": [
        "field",
        "direction"
      ] 
    }
  },
  "required": [
    "how-many-per-page",
    "page-number"
  ]
}

Language

Language-get

To retrieve a list of all available languages, you must in the database you must GET the following body to the BACKENDURL/language/get

No Authorization is required

{
    "how-many-per-page": 2,
    "page-number": 0,
    "sort-by": {"field":"iso_name", "direction":"ASC"},
    "allow-franco-arabic": false
}

If you want to include “franco-arabic” then you must set the json variable “allow-franco-arabic” to true Here is a typical result of such a call:

{
    "status": 200,
    "total-count": 2,
    "page": 0,
    "data": [
        {
            "id": 3,
            "iso_name": "en-US",
            "display_name_english": "English",
            "display_name_native": "English",
            "type": "LtoR"
        },
        {
            "id": 4,
            "iso_name": "sv",
            "display_name_english": "Swedish",
            "display_name_native": "Svenska",
            "type": "LtoR"
        }
    ]
}

Note that “total-count” is not necessarily the number of records returned but it is the total number of results found. With this number you can count the total number of pages in the result = “total-count” divided by “how-many-per-page”. The actual number returned is limited by the call field “how-many-per-page”

Here is the json schema that defines the body of this REST API call.

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "how-many-per-page": {
      "type": "integer"
    },    
    "page-number": {
      "type": "integer"
    },
    "filter-iso_name": {
      "type": "string"
    },        
    "allow-franco-arabic": {
      "type": "boolean"
    },        
    "sort-by": {
      "type": "object",
      "properties": {
        "field": {
          "type": "string",
          "enum": [
            "iso_name",
            "display_name_english",
            "display_name_native"
          ]          
        },
        "direction": {
          "type": "string",
          "enum": [
            "ASC",
            "DESC"
          ]          
        }
      },
      "required": [
        "field",
        "direction"
      ] 
    }
  },
  "required": [
    "how-many-per-page",
    "page-number"
  ]
}

Notification

Notification get

To retrieve the logged in users notifications, you must GET to the BACKENDURL/notification/get

And include the header Authorization mentioned in [Login]

The body must include the following:

{
    "only-unread":true
}

Where if “only-unread” is true then only the unread notifications will be retrieved. if false, then all notifications are retrieved. The way we determine if the notifications are read or not is by using the https://github.com/thomasdilts/churchsongs/wiki/Notification-userupdate where you set the last_notification_date.

Here is a typical result of such a call:

{
    "status": 200,
    "total-count": 2,
    "data": [
        {
            "updated": "2023-08-08 10:15:43",
            "title": "Malek has promised freeze on functionality",
            "body": "Because of extreme pressure from a certain member of the Zamar team, Malek has promised that there will be no more new functionality added to Zamar until after the first release."
        },
        {
            "updated": "2023-08-08 09:26:18",
            "title": "New notification system",
            "body": "Zamar now has a notification system where you will get all the latest and important zamar news. Each time a new notification exists you will immediately get  this dialog. If you want to see old notifications you can go to your profile menu and select \"Notifications.\""
        }
    ]
}

Where the “updated” field is the field that would be returned in the call to https://github.com/thomasdilts/churchsongs/wiki/Notification-userupdate

The specification of how the body will look is the following:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "only-unread": {
      "type": "boolean"
    }
  },
  "required": [
    "only-unread"
  ]
}

Notification update user

To show that the user has read the notifications, you must POST to the BACKENDURL/notification/updateuser

And include the header Authorization mentioned in [Login]

This API is a repeat of the [User profile change] which could also be used to set the last_notification_date. This notification system only records the “last notification read date” which means that you can’t just pick and choose which ones to read individually and think that the system will remember which ones were read.

The body must include the following:

{
    "last_notification_date":"2023-08-08 09:26:18"
}

Where the last_notification_date is gotten from a call to https://github.com/thomasdilts/churchsongs/wiki/Notification-get and the “update” field of the last notification that the user read.

Here is a typical result of such a call:

{
    "status": 200
}

The specification of how the body will look is the following:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "last_notification_date": {
      "type": "string"
    }
  },
  "required": [
    "last_notification_date"
  ]
}

Policies

Policies-get

To get the policies, POST a message to BACKENDURL/site/policies with NO body:

You will receive an answer back with the following confirmation if it went ok.

{
    "status": 200,
    "privacy": "H4sICKxoCWUAA3ByaXZhY3kuaHRtbADtvety28a6Jvx and a lot more ==",
    "terms": "H4sICEtpCWUAA3Rlcm1zLmh0bWwA7D1rcxq7kt+3av+ and a lot more =="
}

The format of the privacy and terms field is that they are an HTML file that is compressed with GZIP compression and then converted to BASE64.

Site access

Signup

In order to signup with the REST API you need to POST a message to BACKENDURL/site/signup with the following body:

{
    "email":"example@example.com",
    "firstname":"my name",
    "lastname":"my last name",
    "language":"en-US",
    "songlanguage":"en-US",
    "songrole":"Leader",
    "password":"mysupersecretpassword"
}

You will receive an answer back with the following confirmation if it went ok. Otherwise an error message.

{
"status":201,
"message":"Check your email to verify your account",
"data":""
}

Note that your account is not valid until you click on the link in your “confirmation email.” This of course means that if you gave a false email then you will never be able to access the REST API.

Country, birthyear, and Gender are NOT required for backwards compatibility but should be included always.

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "email": {
      "type": "string"
    },
    "firstname": {
      "type": "string"
    },
    "lastname": {
      "type": "string"
    },
    "songrole": {
      "type": "string",
      "enum": [
        "Guitar",
        "Ukulele",
        "Piano",
        "Singer",
        "Leader",
        "Other"
      ]      
    },
    "password": {
      "type": "string"
    },
    "language": {
      "type": "string"
    },
    "songlanguage": {
      "type": "string"
    },
    "country": {
      "type": "string"
    },
    "birthyear": {
      "type": "integer"
    },
    "gender": {
      "type": "string",
      "enum": [
        "Male",
        "Female"
      ]
    }
  },
  "required": [
    "email",
    "firstname",
    "lastname",
    "songrole",
    "password",
    "language",
    "songlanguage"
  ]
}

Contact

In order to contact with the REST API you need to POST a message to BACKENDURL/site/contact with the following body:

{
    "email":"example@example.com",
    "name":"my name",
    "subject":"My problem is this",
    "message":"You really need to get the first version out.\\r\\nThanks\\r\\nMy Name",
    "language":"ar"
}

Use \r\n to represent a new line. The user will immediately get an email confirming thier contact request. You will receive an answer back with the following confirmation if it went ok. Otherwise an error message.

{
"status":200,
"message":"Success"
}

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "email": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "subject": {
      "type": "string"
    },
    "message": {
      "type": "string"
    },
    "language": {
      "type": "string"
    }
  },
  "required": [
    "email",
    "name",
    "subject",
    "message",
    "language"
  ]
}

Set password

When you recieve a deeplink to change a password email, you must call this REST API to POST a message to BACKENDURL/site/setpassword with the following body:

{
    "setpasswordtoken":"Mwv_kSsqhTWsYPFpvTA_4o1NFHHpfUGF_1705587480",
    "password":"SuperSecretPassword"
}

PLEASE make sure they give a decent password. At least 8 characters. You will receive an answer back with the following confirmation if it went ok. Otherwise an error message. You may continue to login for that user

{
  "status":200,
  "message":"Successful password change. Proceed to login.",
  "data":"Mwv_kSsqhTWsYPFpvTA_4o1NFHHpfUGF_1705587480"
}

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "setpasswordtoken": {
      "type": "string"
    },
    "password": {
      "type": "string"
    }
  },
  "required": [
    "setpasswordtoken",
    "password"
  ]
}

VerifyEmail

When you recieve a deeplink to verify an email, you must call this REST API to POST a message to BACKENDURL/site/verifyemail with the following body:

{
    "verifytoken":"Mwv_kSsqhTWsYPFpvTA_4o1NFHHpfUGF_1705587480",
}

You will receive an answer back with the following confirmation if it went ok. Otherwise an error message. You may continue to login for that user

{
  "status":200,
  "message":"Successful verification. Proceed to login.",
  "data":"Mwv_kSsqhTWsYPFpvTA_4o1NFHHpfUGF_1705587480"
}

PLEASE NOTE that if the user is already verified then you will get the following answer:

{
    "status": 400,
    "message": "Already verified",
    "data": "Mwv_kSsqhTWsYPFpvTA_4o1NFHHpfUGF_1705587480"
}

You need to check for the above answer because it will happen often that they hit the deeplink more than once.

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "verifytoken": {
      "type": "string"
    }
  },
  "required": [
    "verifytoken"
  ]
}

Resend verification email

If you have already signed up with the REST API but you need another verification email to verify your account, POST a message to BACKENDURL/site/resendverify with the following body. You can also call this to verify a new email HOWEVER the email in the following body is the OLD email and not the new email!

{
    "email":"example@example.com"
}

You will receive an answer back with the following confirmation if it went ok.

{
    "status": 200,
    "message": "Success. Email sent",
    "data": "i6TUM2yj18RYiNopQBHNoH9nrMg6JuCB_1695111209"
}

Where the data field contains the actual verification-token.

Otherwise, if there is an error it will look like one of these

{
    "status": 400,
    "message": "There is no user with this email address that is awaiting verification.",
    "data": "example@example.com"
}
{
    "status": 500,
    "message": "Something went wrong sending the email. Try again later.",
    "data": "i6TUM2yj18RYiNopQBHNoH9nrMg6JuCB_1695111209"
}

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "email": {
      "type": "string"
    }
  },
  "required": [
    "email"
  ]
}

Login

In order to login with the REST API you need to POST a message to BACKENDURL/site/login with the following body:

{
"email":"example@example.com",
"password":"mypassword",
}

You will receive an answer back with the following confirmation if it went ok. Otherwise an error message.

{
  "status":302,
  "message":"Login Succeed, save your token",
  "data":
  {
    "id":"myname",
    "token":"aBmZ1kZQkTW2QxAeTsgzHjBj095-tpqb",
    "email":"example@example.com"
  }
}

It is this “token” that is very important that you keep. This token is valid for 10 minutes since the last time you used it. Therefore it can be valid indefinately until you don’t use it for 10 minutes.

This token you must put in an “Authorization” header in your future calls to the REST API in order to be authorized to access the database. The header looks like the following:

Authorization Bearer aBmZ1kZQkTW2QxAeTsgzHjBj095-tpqb

Of course you will replace the above aBmZ1kZQkTW2QxAeTsgzHjBj095-tpqb with your own key.

firebase-login

In order to login with google or apple via firebase, the REST API you need to POST a message to is ‘BACKENDURL/site/firebase-login’ with the following body:

{
  "idToken":"aBmZ1kZQkTW2QxAeTsgzHjBj095-tpqb",
  "provider":"google"
}

where the “idToken” must be a valid token that you got from Firebase. “provider” is either ‘google’ or ‘apple’ You will receive an answer back with the following confirmation if it went ok. Otherwise an error message.

{
  "status":302,
  "message":"Login successful",
  "data":
  {
    "id":"1237",
    "token":"aBmZ1kZQkTW2QxAeTsgzHjBj095-tpqb",
    "email":"thomas.dilts60@gmail.com",
    "provider":"google",
    "user_created":false
  }
}

NOTE: This API will create the user if the user does not already exist. Therefore do not call [Signup] . However you probably should call [User profile change] in order to set the important user information. The user_created is true if the user was created by this one firebase-login call. User_created is false if the user already existed. You might need this if you need to add other important information about the user with a call to [User profile change].

It is this “token” in this returned data that is very important that you keep. This token is valid for 10 minutes since the last time you used it. Therefore it can be valid indefinately until you don’t use it for 10 minutes.

This token you must put in an “Authorization” header in your future calls to the REST API in order to be authorized to access the database. The header looks like the following:

Authorization Bearer aBmZ1kZQkTW2QxAeTsgzHjBj095-tpqb

Of course you will replace the above aBmZ1kZQkTW2QxAeTsgzHjBj095-tpqb with your own key.

There are many other optional data that you could include in your call to this. See the json body definition.

How It Works:

  1. Token Verification: Validates Firebase ID token (basic JWT validation)
  2. User Lookup: Checks if social account exists or if user exists by email
  3. Account Handling:
  • Existing social account → Login
  • Existing email user → Link social account and login
  • New user → Create account with social data and login
  1. Profile Data: Downloads profile pictures from social providers
  2. Authentication: Returns same token format as your existing login API

Mobile App Integration:

Your mobile apps should:

  1. Authenticate with Firebase (Google/Apple)
  2. Get the Firebase ID token
  3. POST to this endpoint with the token and provider
  4. Use the returned token for subsequent API calls (same as email login [Login])

The required format of the json body you post is:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "properties": {
        "idToken": {
            "type": "string",
            "description": "Firebase ID token from mobile app authentication"
        },
        "provider": {
            "type": "string",
            "enum": ["google", "apple"],
            "description": "Authentication provider (google or apple)"
        },
        "consumer": {
            "type": "string",
            "description": "Optional: App consumer identifier"
        },
        "access_given": {
            "type": "string",
            "description": "Optional: Access given timestamp"
        },
        "language_id": {
            "type": "string",
            "description": "User's interface language ID"
        },
        "songlanguage": {
            "type": "string", 
            "description": "User's primary song language ID"
        },
        "songrole": {
            "type": "string",
            "enum": ["Guitar", "Ukulele", "Piano", "Singer"],
            "description": "User's primary instrument/role"
        },
        "country": {
            "type": "string",
            "description": "User's country"
        },
        "birthyear": {
            "type": "integer"
            "description": "User's birth year"
        },
        "gender": {
            "type": "string",
            "enum": ["Male", "Female"],
            "description": "User's gender"
        }
    },
    "required": ["idToken", "provider"],
    "additionalProperties": false
} 

Reset password

The only way a member can change their password is to POST the following body to the BACKENDURL/site/resetpassword

You will then be sent an email with a link to change your password.

The body should look like the following

{
    "email":"example@example.com"
}

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "email": {
      "type": "string"
    }
  },
  "required": [
    "email"
  ]
}

Rate limiting

If you have the role “Member” which all persons who have signed up get, you will only be allowed to make 100 REST API calls in 10 minutes. If you go over that you will get an error message. Any other roll has no limitations on the number of calls per unit of time. You can always see in the headers you receive back what is the status of your rate limitation. See the headers:

X-Rate-Limit-Limit 100

X-Rate-Limit-Remaining  99

Where the X-Rate-Limit-Limit is the total number of calls per 10 minutes you are allowed. The X-Rate-Limit-Remaining is how many calls you have left in your 10 minutes.

When you exceed this limit you will get the following error:

{    
"name": "Too Many Requests",
"message": "Rate limit exceeded.",
"code": 0,
"status": 429,
"type": "yii\\web\\TooManyRequestsHttpException"
}

Policies

To get the policies, POST a message to BACKENDURL/site/policies with NO body:

You will receive an answer back with the following confirmation if it went ok.

{
    "status": 200,
    "privacy": "H4sICKxoCWUAA3ByaXZhY3kuaHRtbADtvety28a6Jvx and a lot more ==",
    "terms": "H4sICEtpCWUAA3Rlcm1zLmh0bWwA7D1rcxq7kt+3av+ and a lot more =="
}

The format of the privacy and terms field is that they are an HTML file that is compressed with GZIP compression and then converted to BASE64.

Set

Set add

To add a list of songs to the database you must POST the following body to the BACKENDURL/list/add

And include the header Authorization mentioned in [Login]

{
  "name": "my second song list",
  "custom_date": "2023-01-01 11:00:01",
  "songs": [
    {
      "type":"song",
      "song-id": 557,
      "keynote": "C",
      "orderof":"V1 C1 V2 break C1",
      "notes": "Here are my notes for this song"
    },
    {
      "type":"comment",
      "notes":"Here is a comment that goes between songs",
      "iso-language":"en-US",
      "title":"Important comment"
    },
    {
      "type":"song",
      "song-id": 337
    }
  ]
}

At the very least, the minimum you can add is just the list name as follows:

{
  "name": "my second song list"
}

There are clearly two types of inputs to a list, Song or Comment. Each has their own valid fields.

Type required fields optional fields Description
song type, song_id keynote, orderof, notes This is the description of a song with notes. If orderof or keynote are left out then the songs default orderof and keynote will be used. Note that the song returned in a list/get will be ordered by the orderof field.
comment type, iso-language notes, title This is a comment that goes between songs. The iso-language defines how to align the comment and what font to use.

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "custom_date": {
      "type": "string"
    },    
    "songs": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "song", "comment"
            ]            
          },           
          "song-id": {
            "type": "integer"
          },
          "keynote": {
            "type": "string",
            "enum": [
              "C", "C#","Db", "D", "D#", "Eb", "E", "F", "F#", "Gb", "G", "G#", "Ab", "A", "A#", "Bb", "B","Cm", "C#m","Dbm", "Dm", "D#m", "Ebm", "Em", "Fm", "F#m", "Gbm", "Gm", "G#m", "Abm", "Am", "A#m", "Bbm", "Bm"
            ]
          },
          "notes": {
            "type": "string"
          },
          "orderof": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "iso-language": {
            "type": "string"
          }                    
        },
        "oneOf": [
          {
            "required": [
              "type",
              "song-id"
            ]
          },
          {
            "required": [
              "type",
              "iso-language"
            ]
          }
        ]       
      }
    }
  },
  "required": [
    "name"
  ]
}

Set get

To retrieve Lists you created you in the database you must GET the following body to the BACKENDURL/list/get And include the header Authorization mentioned in [Login]

{
    "how-many-per-page": 20,
    "page-number": 0,
    "sort-by": {"field":"name", "direction":"ASC"},
    "returntype":"ALL"
}

Where here you specify how many records you want to get (how-many-per-page) and which page-number in the result you want to look at. Of course the first time you call this you want “page-number”: 0 . Here also is specified exactly what fields you want returned as show in the “returntype”.

There are 4 field definitions you can/need to send in the body. They are the following:

Field definition format example Description
returntype enum “NAMES”,“DETAILS” or “ALL”. Required “returntype”:“ALL” NAMES means only the names, created and ids of the lists will be returned. DETAILS means that Names and song titles and song notes. ALL means everything including all song texts
sort-by object{field,direction} “field”:“name” “direction”:“ASC” Right now you can only sort on the id,name,custom_date,created (date) of the list ascending or descending. The default is name-ASC
filters array[{field,like}] filters[{“field”:“id”, “like”:“234”}] If you want only one list returned in this example. Filter fields are id, name,custom_date, created
option-chords enum, IncludeInTexts or SeparateFromText “option-chords”:“IncludeInTexts” if you want the chords inside of the texts or outside of the texts. The default is outside if this option is excluded

An example using all the above fields:

{
    "how-many-per-page": 20,
    "page-number": 0,
    "returntype":"ALL",
    "sort-by": {"field":"name", "direction":"ASC"},
    "filters":[{"field": "id", "like":"234"}],
    "option-chords":"IncludeInTexts"
}

However, if you are requesting just one list as in the above call, then there is no reason to include a “sort-by”.

Here is a typical result of such a call for just DETAILS:

{
    "status": 200,
    "total-count": 2,
    "page": 0,
    "data": [
        {
            "id": 33,
            "name": "My new list",
            "custom_date": "2023-01-01 11:00:01",
            "created": "2023-05-11 16:17:51",
            "songs": [
                {
                    "type": "song",
                    "sequence": 0,
                    "list-item-id": 121,
                    "notes": "should be good",
                    "keynote": "C",
                    "orderof": "V1",
                    "song-id": 533,
                    "song": {
                        "id": 533,
                        "title": "أهديك كل المجد",
                        "title2": ""
                    }
                },
                {
                    "type": "comment",
                    "sequence": 1,
                    "list-item-id": 122,
                    "title": "Here is a comment in the middle",
                    "notes": "sadfas fs adf sda f sadf as dfsa\r\nas fa sdf sa df saf  sadf as df as fas\r\n asf a sf sa df sadf as fd as df asdf",
                    "iso-language": "sv"
                },
                {
                    "type": "song",
                    "list-item-id": 123,
                    "sequence": 2,
                    "notes": "A very nice song",
                    "keynote": "C#",
                    "orderof": "V1",
                    "song-id": 535,
                    "song": {
                        "id": 535,
                        "title": "عين في الضلمه وعين في النور",
                        "title2": ""
                    }
                }
            ]
        },
        {
            "id": 38,
            "name": "Second list",
            "created": "2023-05-25 10:45:39",
            "songs": [
                {
                    "type": "song",
                    "list-item-id": 124,
                    "sequence": 0,
                    "notes": "Test on a second list",
                    "keynote": "Dm",
                    "orderof": "V1 break V1",
                    "song-id": 265,
                    "song": {
                        "id": 265,
                        "title": "أنا شفت بستان",
                        "title2": ""
                    }
                },
                {
                    "type": "song",
                    "list-item-id": 125,
                    "sequence": 1,
                    "notes": "Another song",
                    "keynote": "Ebm",
                    "orderof": "V1",
                    "song-id": 567,
                    "song": {
                        "id": 567,
                        "title": "ربي أنت حياتي",
                        "title2": ""
                    }
                }
            ]
        }
    ]
}

Here is another returned body with the ALL returntype. This contains probably all the possible combinations that can be returned. Note that one of the paragraphs returned contains only “break”: “true” which means a page break was requested there.

{
    "status": 200,
    "total-count": 2,
    "page": 0,
    "data": [
        {
            "id": 33,
            "name": "My new list",
            "created": "2023-05-11 16:17:51",
            "songs": [
                {
                    "type": "song",
                    "list-item-id": 126,
                    "sequence": 0,
                    "notes": "should be good",
                    "keynote": "C",
                    "orderof": "V1",
                    "song-id": 533,
                    "song": {
                        "id": 533,
                        "status": "Published",
                        "title": "أهديك كل المجد",
                        "title2": "",
                        "orderof": "V1",
                        "keynote": "C",
                        "created": "2023-05-04 10:42:14",
                        "updated": "2023-05-04 10:42:28",
                        "firstname": "Elias",
                        "bibleverse": "Heb 2 : 12, Job 42 : 2, Ps 72 : 18",
                        "iso_name": null,
                        "tags": [
                            {
                                "name": "التسبيح - الهتاف",
                                "iso_name": "ar"
                            }
                        ],
                        "authors": [
                            {
                                "id": 50,
                                "name": "فيليب ويصا",
                                "type": "Translated"
                            },
                            {
                                "id": 42,
                                "name": "فيليب ويصا",
                                "type": "Artist"
                            },
                            {
                                "id": 43,
                                "name": "لحن أجنبي",
                                "type": "Music"
                            }
                        ],
                        "paragraphs": [
                            {
                                "type": "Verse 1",
                                "rows": [
                                    {
                                        "text": "(أهديك كل المـــ[C]جــ[Cmaj7]ــد والكرا[Am]مة"
                                    },
                                    {
                                        "text": "أرفع[Dm] يداي نحوك وأ[F]سبح اسمك[G7])2"
                                    },
                                    {
                                        "text": "أنت عظ[C]يم ليس مث[E7]لك يا ر[Am]ب"
                                    },
                                    {
                                        "text": "تصنع العجائب[F] [Dm]  تصنع[F] العجائ[G7]ب"
                                    },
                                    {
                                        "text": "أنت عظ[C]يم ليس مث[E7]لك يا ر[Am]ب"
                                    },
                                    {
                                        "text": "تستطيع كل ش[F]يء[Dm]  تستط[F]يع ك[G7]ل ش[C]يء"
                                    }
                                ]
                            }
                        ]
                    }
                },
                {
                    "type": "comment",
                    "list-item-id": 127,
                    "sequence": 1,
                    "title": "Here is a comment in the middle",
                    "notes": "sadfas fs adf sda f sadf as dfsa\r\nas fa sdf sa df saf  sadf as df as fas\r\n asf a sf sa df sadf as fd as df asdf",
                    "iso-language": "sv"
                },
                {
                    "type": "song",
                    "list-item-id": 128,
                    "sequence": 2,
                    "notes": "A very nice song",
                    "keynote": "C#",
                    "orderof": "V1",
                    "song-id": 535,
                    "song": {
                        "id": 535,
                        "status": "Published",
                        "title": "عين في الضلمه وعين في النور",
                        "title2": "",
                        "orderof": "V1",
                        "keynote": "C",
                        "created": "2023-05-04 13:11:10",
                        "updated": "2023-05-04 13:11:14",
                        "firstname": "Malek",
                        "bibleverse": "",
                        "iso_name": null,
                        "tags": [
                            {
                                "name": "مدارس الأحد",
                                "iso_name": "ar"
                            }
                        ],
                        "authors": [
                            {
                                "id": 30,
                                "name": "فريق الحياة الأفضل",
                                "type": "Artist"
                            },
                            {
                                "id": 118,
                                "name": "صفاء صبحى",
                                "type": "Text"
                            },
                            {
                                "id": 119,
                                "name": "صفاء صبحى",
                                "type": "Music"
                            }
                        ],
                        "paragraphs": [
                            {
                                "type": "Verse 1",
                                "rows": [
                                    {
                                        "text": "عي[C]ن في الضلمة وعين في النور "
                                    },
                                    {
                                        "text": "لأ[Dm] مش ممكن يا[C] شطور"
                                    },
                                    {
                                        "text": "عين في الضلمه وعين في النور "
                                    },
                                    {
                                        "text": "ل[Dm]أ مش ممكن يا[G] شط[C]ور"
                                    },
                                    {
                                        "text": "م[C]ش ممكن نسلك في الضلمه "
                                    },
                                    {
                                        "text": "ونقول احنا ولاد النور"
                                    },
                                    {
                                        "text": "يا[Dm]للا قوام بحياتنا ننور "
                                    },
                                    {
                                        "text": "و الرب يكون بيننا مسرو[G]ر"
                                    },
                                    {
                                        "text": "يا[C]للا قوام بحي[G]اتنا ننور"
                                    },
                                    {
                                        "text": "وا[Dm]لرب يكون بي[G]نا مسر[C]ور"
                                    }
                                ]
                            }
                        ]
                    }
                }
            ]
        },
        {
            "id": 38,
            "name": "Second list",
            "created": "2023-05-25 10:45:39",
            "songs": [
                {
                    "type": "song",
                    "list-item-id": 129,
                    "sequence": 0,
                    "notes": "Test on a second list",
                    "keynote": "Dm",
                    "orderof": "V1 break V1",
                    "song-id": 265,
                    "song": {
                        "id": 265,
                        "status": "Published",
                        "title": "أنا شفت بستان",
                        "title2": "",
                        "orderof": "V1",
                        "keynote": "C",
                        "created": "2023-04-17 23:11:30",
                        "updated": "2023-04-19 10:51:09",
                        "firstname": "Malek",
                        "bibleverse": "",
                        "iso_name": null,
                        "authors": [
                            {
                                "id": 30,
                                "name": "فريق الحياة الأفضل",
                                "type": "Artist"
                            },
                            {
                                "id": 31,
                                "name": "منال سمير",
                                "type": "Text"
                            },
                            {
                                "id": 32,
                                "name": "منال سمير",
                                "type": "Music"
                            }
                        ],
                        "paragraphs": [
                            {
                                "type": "Verse 1",
                                "rows": [
                                    {
                                        "text": "أن[C]ا شفت بستا[Em]ن وبقيت سرح[F]ان "
                                    },
                                    {
                                        "text": "ف[Dm]ي الورود و[G]الأزهار من كل الألو[C]ان"
                                    },
                                    {
                                        "text": "وسمعت العصاف[Em]ير بتقول ألح[F]ان"
                                    },
                                    {
                                        "text": "و[Dm]الشمس بت[G]صبَّح ع[G7]لى كل الأغص[C]ان"
                                    },
                                    {
                                        "text": "واليوم بيقول ل[F]تاني ش[Dm]وف الله إيه ا[G]داني "
                                    },
                                    {
                                        "text": "و[C]شفت النمل[F]ة م[G]شغولة بشغل[C]ة"
                                    },
                                    {
                                        "text": "والنحلة بت[F]عمل بيت م[Dm]ن الشمع بت[G]عمل بيت"
                                    },
                                    {
                                        "text": "و[C]أنا لما شفت[F] البست[C]ان "
                                    },
                                    {
                                        "text": "بق[G7]يت سرح[C]ان يار[Cm]بي"
                                    },
                                    {
                                        "text": "أ[Cm]سمع صوتك الجم[Fm]يل "
                                    },
                                    {
                                        "text": "ص[G7]بح وظهر وعصر ول[Cm]يل"
                                    },
                                    {
                                        "text": "وأسبحك كل ي[Fm]وم "
                                    },
                                    {
                                        "text": "م[G]ع الشمس والنج[Cm]وم"
                                    }
                                ]
                            },
                            {
                                "break": "true"
                            },
                            {
                                "type": "Verse 1",
                                "rows": [
                                    {
                                        "text": "أن[C]ا شفت بستا[Em]ن وبقيت سرح[F]ان "
                                    },
                                    {
                                        "text": "ف[Dm]ي الورود و[G]الأزهار من كل الألو[C]ان"
                                    },
                                    {
                                        "text": "وسمعت العصاف[Em]ير بتقول ألح[F]ان"
                                    },
                                    {
                                        "text": "و[Dm]الشمس بت[G]صبَّح ع[G7]لى كل الأغص[C]ان"
                                    },
                                    {
                                        "text": "واليوم بيقول ل[F]تاني ش[Dm]وف الله إيه ا[G]داني "
                                    },
                                    {
                                        "text": "و[C]شفت النمل[F]ة م[G]شغولة بشغل[C]ة"
                                    },
                                    {
                                        "text": "والنحلة بت[F]عمل بيت م[Dm]ن الشمع بت[G]عمل بيت"
                                    },
                                    {
                                        "text": "و[C]أنا لما شفت[F] البست[C]ان "
                                    },
                                    {
                                        "text": "بق[G7]يت سرح[C]ان يار[Cm]بي"
                                    },
                                    {
                                        "text": "أ[Cm]سمع صوتك الجم[Fm]يل "
                                    },
                                    {
                                        "text": "ص[G7]بح وظهر وعصر ول[Cm]يل"
                                    },
                                    {
                                        "text": "وأسبحك كل ي[Fm]وم "
                                    },
                                    {
                                        "text": "م[G]ع الشمس والنج[Cm]وم"
                                    }
                                ]
                            }
                        ]
                    }
                },
                {
                    "type": "song",
                    "list-item-id": 130,
                    "sequence": 1,
                    "notes": "Another song",
                    "keynote": "Ebm",
                    "orderof": "V1",
                    "song-id": 567,
                    "song": {
                        "id": 567,
                        "status": "Published",
                        "title": "ربي أنت حياتي",
                        "title2": "",
                        "orderof": "V1",
                        "keynote": "C",
                        "created": "2023-05-14 10:52:40",
                        "updated": "2023-05-14 10:52:42",
                        "firstname": "Malek",
                        "bibleverse": "",
                        "iso_name": null,
                        "tags": [
                            {
                                "name": "مدارس الأحد",
                                "iso_name": "ar"
                            }
                        ],
                        "authors": [
                            {
                                "id": 30,
                                "name": "فريق الحياة الأفضل",
                                "type": "Artist"
                            },
                            {
                                "id": 32,
                                "name": "منال سمير",
                                "type": "Music"
                            },
                            {
                                "id": 31,
                                "name": "منال سمير",
                                "type": "Text"
                            }
                        ],
                        "paragraphs": [
                            {
                                "type": "Verse 1",
                                "rows": [
                                    {
                                        "text": "رب[C]ي إنت حي[F]اتي ك[G]ل حبي وأو[Em]قاتـي "
                                    },
                                    {
                                        "text": "م[F]لك إيديك يا إله[Dm]ي حت[G]ى وقت لع[G7]بي"
                                    },
                                    {
                                        "text": "ما[C] فيش زيك ف[F]ي الدنيا قل[G]بي معاك ثانية[Em] بثانية "
                                    },
                                    {
                                        "text": "اد[F]يتني السما[Dm] هدية لي[G]ك كل قلبـ[C]ي"
                                    },
                                    {
                                        "text": "دا[A7]يماً إيدي في إي[Dm]دك وع[G7]يني عل[C]يـك"
                                    },
                                    {
                                        "text": " وفي[Am7] لساني ترنيم[Dm]ة وق[G]لبي فرحان بيك"
                                    },
                                    {
                                        "text": "ع[A7]لشان إنت فدي[Dm]تني و[F]إنت اللي رع[G]يتني"
                                    },
                                    {
                                        "text": " لـ[C]و فتحت عين[F]يَّ و[G]بصيت حوالـ[Em]يَّ"
                                    },
                                    {
                                        "text": "مـ[F]ش هالاقـي زيـ[Dm]ك حـ[G]لـو لـيَّ[C]"
                                    }
                                ]
                            }
                        ]
                    }
                }
            ]
        }
    ]
}

Note that “total-count” is not necessarily the number of records returned but it is the total number of results found. With this number you can count the total number of pages in the result = “total-count” divided by “how-many-per-page”. The actual number returned is limited by the call field “how-many-per-page”

Here is the json schema that defines the body of this REST API call.

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "how-many-per-page": {
      "type": "integer"
    },    
    "page-number": {
      "type": "integer"
    },
    "filters": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "field": {
            "type": "string",
            "enum": ["id","name","created","custom_date"]
          },
          "like": {
            "type": "string"
          }
        },
        "required": [
          "field",
          "like"
        ]    
      },
      "minItems": 1,
      "uniqueItems": true       
    },       
    "returntype":{
      "type": "string",
      "enum": ["NAMES","DETAILS","ALL"]  
    },   
    "option-chords": {
      "type": "string",
      "enum": [
        "IncludeInTexts",
        "SeparateFromText"
      ]
    },    
    "sort-by": {
      "type": "object",
      "properties": {
        "field": {
          "type": "string",
          "enum": [
            "id",
            "name",
            "created",
            "custom_date"
          ]          
        },
        "direction": {
          "type": "string",
          "enum": [
            "ASC",
            "DESC"
          ]          
        }
      },
      "required": [
        "field",
        "direction"
      ] 
    }
  },
  "required": [
    "how-many-per-page",
    "page-number",
    "returntype"
  ]
}

Set add songs

To add one or more songs/comments to an existing list in the database you must POST the following body to the BACKENDURL/list/addsongs And include the header Authorization mentioned in [Login]

{
  "id2change":9,
  "songs": [
    {
      "type":"song",
      "song-id": 557,
      "keynote": "C",
      "orderof":"V1 C1 V2 break C1",
      "notes": "Here are my notes for this song"
    },
    {
      "type":"comment",
      "notes":"Here is a comment that goes between songs",
      "iso-language":"en-US",
      "title":"Important comment"
    },
    {
      "type":"song",
      "song-id": 337
    }
  ]
}

Note that this will only add songs/comments to the list and no other changes will be made to the list.

The id2change is gotten from the [set get] call and the “id” just before the list name

 "status": 200,
   "total-count": 1,
   "page": 0,
   "data": [
       {
     !!!! "id": 6, !!!!!!
           "name": "my second list",
           "created": "2023-03-16 21:25:44",
           "songs": [
               {
                   "id": 18,

Please see the definition of [[List add]] for more details on what is required in the body of this change.

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "id2change": {
      "type": "integer"
    },          
    "songs": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "song", "comment"
            ]            
          },           
          "song-id": {
            "type": "integer"
          },
          "keynote": {
            "type": "string",
            "enum": [
              "C", "C#","Db", "D", "D#", "Eb", "E", "F", "F#", "Gb", "G", "G#", "Ab", "A", "A#", "Bb", "B","Cm", "C#m","Dbm", "Dm", "D#m", "Ebm", "Em", "Fm", "F#m", "Gbm", "Gm", "G#m", "Abm", "Am", "A#m", "Bbm", "Bm"
            ]
          },
          "notes": {
            "type": "string"
          },
          "orderof": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "iso-language": {
            "type": "string"
          }                    
        },
        "oneOf": [
          {
            "required": [
              "type",
              "song-id"
            ]
          },
          {
            "required": [
              "type",
              "iso-language"
            ]
          }
        ]       
      }
    }
  },
  "required": [
    "id2change"
  ]
}

Set getonesong

To retrieve just one song that is in a List you created in the database you must GET the following body to the BACKENDURL/list/getonesong And include the header Authorization mentioned in [Login]

This call is somewhat unnecessary because the [set get] will also get ALL the songs in the list. But in the case that you want just one song and not ALL the songs then this call is what you need. This call is equivalent to [set get] with “returntype”:“ALL” because everything about the song/comment will be returned.

{
    "list-item-id": 20,
    "option-chords":"IncludeInTexts"
}

Where here you specify what list-item and what song in that list you want to get. Note that it may not be a song but a comment as well. The list-item-id is gotten from a call to [[List get]].

A typical return from this call would look like the following:

{
    "status": 200,
    "data": [
        {
            "type": "song",
            "list-item-id": 20,
            "sequence": 0,
            "notes": "Here are my notes for this song",
            "keynote": "C",
            "orderof": "V1 C1 break V2 C1",
            "song-id": 557,
            "song": {
                "id": 557,
                "status": "Published",
                "title": "أدخل لقدسك",
                "title2": "",
                "orderof": "V1 C1 V2",
                "keynote": "C",
                "created": "2023-05-14 10:04:12",
                "updated": "2023-05-14 10:04:35",
                "firstname": "Elias",
                "bibleverse": "Ps 4 : 7, Ps 43 : 3-4",
                "iso_name": null,
                "tags": [
                    {
                        "name": "الاعداد",
                        "iso_name": "ar"
                    },
                    {
                        "name": "الشفاء",
                        "iso_name": "ar"
                    },
                    {
                        "name": "انتظار الرب",
                        "iso_name": "ar"
                    },
                    {
                        "name": "طلب وجه الرب",
                        "iso_name": "ar"
                    }
                ],
                "authors": [
                    {
                        "id": 65,
                        "name": "عادل بشارة",
                        "type": "Music"
                    },
                    {
                        "id": 42,
                        "name": "فيليب ويصا",
                        "type": "Artist"
                    },
                    {
                        "id": 64,
                        "name": "محب ميلاد",
                        "type": "Text"
                    },
                    {
                        "id": 66,
                        "name": "محب ميلاد",
                        "type": "Music"
                    }
                ],
                "paragraphs": [
                    {
                        "type": "Verse 1",
                        "rows": [
                            {
                                "text": "أ[C]دخل لقدس[Cmaj7]ك أت[C]رجى وج[Cmaj7]هك "
                            },
                            {
                                "text": "ط[Dm]البًا حض[G]ور روحك[C] [G7]"
                            },
                            {
                                "text": "أ[C]نت إل[Cmaj7]هي [C]ملجأي وص[A7]خرة "
                            },
                            {
                                "text": "خلا[Dm]صي مم[G7]ن أخ[C]اف [E7]"
                            }
                        ]
                    },
                    {
                        "type": "Chorus 1",
                        "rows": [
                            {
                                "text": "فتعـ[Am]ال بروحـك[Dm] أ[F]بصر مجـدك[G] [G7]"
                            },
                            {
                                "text": "ال[C]آن سلطان الل[Em]ه لنا شف[F]اء من يدك[G] [G7]"
                            },
                            {
                                "text": "الآ[C]ن أنهار مي[Em]اه لنا حي[F]ـاة بروحك[G] [G7]"
                            }
                        ]
                    },
                    {
                        "break": "true"
                    },
                    {
                        "type": "Verse 2",
                        "rows": [
                            {
                                "text": "ر[C]بي أنت نهر[Cmaj7] ت[C]فيض بالح[Cmaj7]ب "
                            },
                            {
                                "text": "تث[Dm]مر في فرح[C]ا[G7]"
                            },
                            {
                                "text": "أ[C]نت سلام[Cmaj7]ي ف[C]يك أم[A7]اني "
                            },
                            {
                                "text": "ل[Dm]يس لي سو[G7]اك يا ال[C]له [E7]"
                            }
                        ]
                    },
                    {
                        "type": "Chorus 1",
                        "rows": [
                            {
                                "text": "فتعـ[Am]ال بروحـك[Dm] أ[F]بصر مجـدك[G] [G7]"
                            },
                            {
                                "text": "ال[C]آن سلطان الل[Em]ه لنا شف[F]اء من يدك[G] [G7]"
                            },
                            {
                                "text": "الآ[C]ن أنهار مي[Em]اه لنا حي[F]ـاة بروحك[G] [G7]"
                            }
                        ]
                    }
                ]
            }
        }
    ]
}

Keep in mind that this might also return a comment block as in the following:

{
    "status": 200,
    "data": [
        {
            "type": "comment",
            "list-item-id": 121,
            "sequence": 1,
            "title": "Important comment",
            "notes": "Here is a comment that goes between songs",
            "iso-language": "en-US"
        }
    ]
}

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "list-item-id": {
      "type": "integer"
    },   
    "option-chords": {
      "type": "string",
      "enum": [
        "IncludeInTexts",
        "SeparateFromText"
      ]
    }
  },
  "required": [
    "list-item-id"
  ]
}

Set deletesong

To delete just one song that is in a List you created in the database you must post the following body to the BACKENDURL/list/deletesong And include the header Authorization mentioned in [Login]

The following is an example of how the body of the message would look

{
    "list-item-id": 20
}

Where here you specify what list-item you want to delete. Note that it may not be a song but a comment as well. The list-item-id is gotten from a call to [[List get]].

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "list-item-id": {
      "type": "integer"
    }
  },
  "required": [
    "list-item-id"
  ]
}

Set changesong

To change just one song that is in a List you created in the database you must post the following body to the BACKENDURL/list/changesong And include the header Authorization mentioned in [Login]

The following is an example of the body of the message.

{
    "list-item-id":494,
    "keynote": "Cm",
    "orderof":"V1 C1 V2 C1",
    "notes": "Here are my CHANGED notes for this song"
}

Where here you specify what list-item you want to change. Note that it may not be a song but a comment as well. The list-item-id is gotten from a call to [[List get]].

If it’s a song, then the fields

  • notes
  • keynote
  • orderof

are used. If it’s a comment then the fields

  • notes
  • title
  • iso-language

are used.

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "list-item-id": {
      "type": "integer"
    },         
    "keynote": {
      "type": "string",
      "enum": [
        "C", "C#","Db", "D", "D#", "Eb", "E", "F", "F#", "Gb", "G", "G#", "Ab", "A", "A#", "Bb", "B","Cm", "C#m","Dbm", "Dm", "D#m", "Ebm", "Em", "Fm", "F#m", "Gbm", "Gm", "G#m", "Abm", "Am", "A#m", "Bbm", "Bm"
      ]
    },
    "notes": {
      "type": "string"
    },
    "orderof": {
      "type": "string"
    },
    "title": {
      "type": "string"
    },
    "iso-language": {
      "type": "string"
    }                    
  },
  "required": [
    "list-item-id"
  ]
}

Set sequence

To change the sequence of the songs in a List you created in the database you must post the following body to the BACKENDURL/list/sequence And include the header Authorization mentioned in [Login]

The following is an example of how the body of the message would look

{
    "list-item-ids":[619,494,496]
}

Where here you specify the new sequence of the list-items by showing the order of their ids. The list-item-id is gotten from a call to [[List get]].

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "list-item-ids": {
      "type": "array",
      "minItems": 2,
      "uniqueItems": true,
      "items": {
        "type": "integer"
      }   
    }  
  },
  "required": [
    "list-item-ids"
  ]
}

Set share by email

To share a list with one or more persons via email you must POST the following body to the BACKENDURL/list/share

And include the header Authorization mentioned in [Login]

A typical call would look like the following

{
    "list-id":223,
    "emails":["email1@example.com","email2@example.com","email3@example.com"]
}

Where the list-id here is the id of the UserList object you get from a call to [set get]

and the “id” just before the list name

 "status": 200,
   "total-count": 1,
   "page": 0,
   "data": [
       {
     !!!! "id": 6, !!!!!!
           "name": "my second list",
           "created": "2023-03-16 21:25:44",
           "songs": [
               {
                   "id": 18,

Each email will be sent a mail informing them about the share with a link to the webpage to get/see the share. Eventually the Zamar app could create Deep Links to receive these links from the mail and proceed to add the shared list to the persons own lists with a call to [[List shareadd]].

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "list-id": {
      "type": "integer"
    },
    "emails": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "minItems": 1,
      "uniqueItems": true        
    }
  },
  "required": [
    "list-id",
    "emails"
  ]
}

To get a link to share a list with any number of persons, you must POST the following body to the BACKENDURL/list/sharegetlink

And include the header Authorization mentioned in [Login]

Note that the link is only good for 1 week. This time should be all that is needed because once someone uses the link they will usually automatically get the list copied to their own group of lists immediately.

A typical call would look like the following

{
    "list-id":223
}

Where the list-id here is the id of the UserList object you get from a call to [set get]

and the “id” just before the list name

 "status": 200,
   "total-count": 1,
   "page": 0,
   "data": [
       {
     !!!! "id": 6, !!!!!!
           "name": "my second list",
           "created": "2023-03-16 21:25:44",
           "songs": [
               {
                   "id": 18,

You will receive a link that you can then share to anyone you wish as shown in the following received json

{
    "status": 200,
    "message": "Success.",
    "share-link": "https://jesusislord.se/zamar/frontend/web/index.php/list/sharestartuse?token=UfyTqN9doyauoj5kBcIeiYG3WeFi_C-B_1687203535"
}

You may then share this link with anyone to get/see the share. Eventually the Zamar app could create Deep Links to receive these links from this ‘share-link’ and proceed to add the shared list to the persons own lists with a call to [[List shareadd]].

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "list-id": {
      "type": "integer"
    }
  },
  "required": [
    "list-id"
  ]
}

Set shareadd

From a Deep Link = “FRONTENDURL/list/sharestartuse?token=OHbA0Ix4oAcXHEY5sSTkleLKjmPAi2Fh_1684852890” received in an email from a previous call to [[List share]], you can add a shared list to a persons own lists by POSTing the following body to the BACKENDURL/list/shareadd

And include the header Authorization mentioned in [Login]

A typical call would look like the following

{
    "share-token":"OHbA0Ix4oAcXHEY5sSTkleLKjmPAi2Fh_1684852890",
    "option-chords":"IncludeInTexts"
}

You will then recieve back an ID and complete list so you can jump directly to the newly created list as shown in the following typical response:

{
    "status": 200,
    "message": "Success. The share is copied to your own sets",
    "data": {
        "id": 50,
        "name": "My new list",
        "created": "2023-05-25 14:51:06",
        "songs": [
            {
                "type": "song",
                "sequence": 0,
                "notes": "should be good",
                "keynote": "C",
                "orderof": "V1",
                "song-id": 533,
                "song": {
                    "id": 533,
                    "status": "Published",
                    "title": "أهديك كل المجد",
                    "title2": "",
                    "orderof": "V1",
                    "keynote": "C",
                    "created": "2023-05-04 10:42:14",
                    "updated": "2023-05-04 10:42:28",
                    "firstname": "Elias",
                    "bibleverse": "Heb 2 : 12, Job 42 : 2, Ps 72 : 18",
                    "iso_name": null,
                    "tags": [
                        {
                            "name": "التسبيح - الهتاف",
                            "iso_name": "ar"
                        }
                    ],
                    "authors": [
                        {
                            "id": 50,
                            "name": "فيليب ويصا",
                            "type": "Translated"
                        },
                        {
                            "id": 42,
                            "name": "فيليب ويصا",
                            "type": "Artist"
                        },
                        {
                            "id": 43,
                            "name": "لحن أجنبي",
                            "type": "Music"
                        }
                    ],
                    "paragraphs": [
                        {
                            "type": "Verse 1",
                            "rows": [
                                {
                                    "text": "(أهديك كل المـــ[C]جــ[Cmaj7]ــد والكرا[Am]مة"
                                },
                                {
                                    "text": "أرفع[Dm] يداي نحوك وأ[F]سبح اسمك[G7])2"
                                },
                                {
                                    "text": "أنت عظ[C]يم ليس مث[E7]لك يا ر[Am]ب"
                                },
                                {
                                    "text": "تصنع العجائب[F] [Dm]  تصنع[F] العجائ[G7]ب"
                                },
                                {
                                    "text": "أنت عظ[C]يم ليس مث[E7]لك يا ر[Am]ب"
                                },
                                {
                                    "text": "تستطيع كل ش[F]يء[Dm]  تستط[F]يع ك[G7]ل ش[C]يء"
                                }
                            ]
                        }
                    ]
                }
            },
            {
                "type": "comment",
                "sequence": 1,
                "title": "Here is a comment in the middle",
                "notes": "sadfas fs adf sda f sadf as dfsa\r\nas fa sdf sa df saf  sadf as df as fas\r\n asf a sf sa df sadf as fd as df asdf",
                "iso-language": "sv"
            },
            {
                "type": "song",
                "sequence": 2,
                "notes": "A very nice song",
                "keynote": "C#",
                "orderof": "V1",
                "song-id": 535,
                "song": {
                    "id": 535,
                    "status": "Published",
                    "title": "عين في الضلمه وعين في النور",
                    "title2": "",
                    "orderof": "V1",
                    "keynote": "C",
                    "created": "2023-05-04 13:11:10",
                    "updated": "2023-05-04 13:11:14",
                    "firstname": "Malek",
                    "bibleverse": "",
                    "iso_name": null,
                    "tags": [
                        {
                            "name": "مدارس الأحد",
                            "iso_name": "ar"
                        }
                    ],
                    "authors": [
                        {
                            "id": 30,
                            "name": "فريق الحياة الأفضل",
                            "type": "Artist"
                        },
                        {
                            "id": 118,
                            "name": "صفاء صبحى",
                            "type": "Text"
                        },
                        {
                            "id": 119,
                            "name": "صفاء صبحى",
                            "type": "Music"
                        }
                    ],
                    "paragraphs": [
                        {
                            "type": "Verse 1",
                            "rows": [
                                {
                                    "text": "عي[C]ن في الضلمة وعين في النور "
                                },
                                {
                                    "text": "لأ[Dm] مش ممكن يا[C] شطور"
                                },
                                {
                                    "text": "عين في الضلمه وعين في النور "
                                },
                                {
                                    "text": "ل[Dm]أ مش ممكن يا[G] شط[C]ور"
                                },
                                {
                                    "text": "م[C]ش ممكن نسلك في الضلمه "
                                },
                                {
                                    "text": "ونقول احنا ولاد النور"
                                },
                                {
                                    "text": "يا[Dm]للا قوام بحياتنا ننور "
                                },
                                {
                                    "text": "و الرب يكون بيننا مسرو[G]ر"
                                },
                                {
                                    "text": "يا[C]للا قوام بحي[G]اتنا ننور"
                                },
                                {
                                    "text": "وا[Dm]لرب يكون بي[G]نا مسر[C]ور"
                                }
                            ]
                        }
                    ]
                }
            }
        ]
    }
}

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "share-token": {
      "type": "string"
    },
    "option-chords": {
      "type": "string",
      "enum": [
        "IncludeInTexts",
        "SeparateFromText"
      ]
    }    
  },
  "required": [
    "share-token"
  ]
}

Set change

To change a list in the database you must POST the following body to the BACKENDURL/list/change And include the header Authorization mentioned in [Login]

{
  "id2change":9,
  "name": "my second song list",
  "custom_date": "2023-01-01 11:00:01",
  "songs": [
    {
      "type":"song",
      "song-id": 557,
      "keynote": "C",
      "orderof":"V1 C1 V2 break C1",
      "notes": "Here are my notes for this song"
    },
    {
      "type":"comment",
      "notes":"Here is a comment that goes between songs",
      "iso-language":"en-US",
      "title":"Important comment"
    },
    {
      "type":"song",
      "song-id": 337
    }
  ]
}

If you don’t include the songs field then the songs will not get erased. But if you include the songs field and empty brackets all the songs will get erased or replaced with what you include in the songs field.

The id2change is gotten from the [set get] call and the “id” just before the list name

 "status": 200,
   "total-count": 1,
   "page": 0,
   "data": [
       {
     !!!! "id": 6, !!!!!!
           "name": "my second list",
           "created": "2023-03-16 21:25:44",
           "songs": [
               {
                   "id": 18,

Please see the definition of [[List add]] for more details on what is required in the body of this change.

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "id2change": {
      "type": "integer"
    },         
    "name": {
      "type": "string"
    },
    "custom_date": {
      "type": "string"
    },   
    "songs": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "song", "comment"
            ]            
          },           
          "song-id": {
            "type": "integer"
          },
          "keynote": {
            "type": "string",
            "enum": [
              "C", "C#","Db", "D", "D#", "Eb", "E", "F", "F#", "Gb", "G", "G#", "Ab", "A", "A#", "Bb", "B","Cm", "C#m","Dbm", "Dm", "D#m", "Ebm", "Em", "Fm", "F#m", "Gbm", "Gm", "G#m", "Abm", "Am", "A#m", "Bbm", "Bm"
            ]
          },
          "notes": {
            "type": "string"
          },
          "orderof": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "iso-language": {
            "type": "string"
          }                    
        },
        "oneOf": [
          {
            "required": [
              "type",
              "song-id"
            ]
          },
          {
            "required": [
              "type",
              "iso-language"
            ]
          }
        ]       
      }
    }
  },
  "required": [
    "id2change",
    "name"
  ]
}

Set delete

To delete one or more lists in the database you must POST the following body to the BACKENDURL/list/delete

And include the header Authorization mentioned in [Login]

A typical call would look like the following

{
    "ids":[23,24]
}

Where the ids here are ids of the UserList objects you get from a call to [set get]

and the “id” just before the list name

 "status": 200,
   "total-count": 1,
   "page": 0,
   "data": [
       {
     !!!! "id": 6, !!!!!!
           "name": "my second list",
           "created": "2023-03-16 21:25:44",
           "songs": [
               {
                   "id": 18,

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "ids": {
      "type": "array",
      "items": {
        "type": "integer"
      }      
    }
  },
  "required": [
    "ids"
  ]
}

Song

Song versions

To retreive the list of latest versions from the database you must POST the following body to the BACKENDURL/song/versions.

NO AUTHORIZATION IS REQUIRED FOR THIS

No body is sent in this and it is a GET protocol.

The typical response is the following

{
    "status": 200,
    "songs": 1349,
    "webApplicationVersion": "0.0.0",
    "databaseVersion": "0.0.0",
    "androidVersion": "0.0.0",
    "iosVersion": "0.0.0",
    "appVersionChanges": "1. No bugs <br />2. 100 times better<br />3. You will love it"
}

Please note that the “songs” field is the number of songs in the status “PUBLISHED”. There will probably be many more songs in other status like “MEDLEY” which are not included in this count.

Song add

To add a song to the database you must POST the following body to the BACKENDURL/song/add And include the header Authorization mentioned in [Login]

{
   "title":"Awesome is our God",
   "orderof":"V1 V2",
   "keynote":"C",
   "iso_name":"en-US",
   "tags":[{"name": "psalmschanged","iso_name":"en-US"},{"name": "lovsong","iso_name":"en-US"}],
   "authors":[{"type":"Text", "name":"Bob"},{"type":"Music", "name":"John"}],
   "paragraphs":[
      {
         "type":"Verse",
         "rows":[
            {
               "text":"He is so awesome",
               "language-iso":"ar"
            },
            {
               "text":"that saved a wretch like me",
               "language-iso":"ar"
            }
         ]
      },
      {
         "type":"Verse",
         "rows":[
            {
               "text":"row 1",
               "language-iso":"ar"
            },
            {
               "text":"row 2",
               "language-iso":"ar"
            }
         ]
      }
   ]
}

Errors will be thrown as well in the following cases: * The rows.langauge-iso does not already exist in the database. * The rows.text has invalid brackets or invalid chords within []. Example [C#m] is acceptable. [Z] is not. * The orderof field must contain a valid ordering of the paragraphs. For instance, “V1 C1 V2 C1 B1”.

’‘’Orderof field’’’

The orderof field must contain a valid ordering of the paragraphs seperated by a space. Usually the Chorus will come several times in a song. With this field you must specify the order. “V” for Verse, “C” for Chorus and “B” for bridge. Bridge will sometimes show up at the end of a song. A typical song would be “V1 R1 V2 R1 V3 R1 B1”.

’‘’Allowed chords’’’

Any chord begining with a valid keynote will be accepted

’‘’Allowed keynote’’’

This program only accepts the following most common keynotes:

"C", "Cm", "C#","C#m","Db", "Dbm", "D", "Dm", "D#", "D#m", "Eb", "Ebm", "E", "Em", "F", "Fm", "F#", 
"F#m", "Gb", "Gbm", "G", "Gm", "G#", "G#m", "Ab", "Abm", "A", "Am", "A#", "A#m", "Bb", "Bbm", "B", "Bm"

The actual definition of how this json must look is the following json schema:

{
    "$schema": "http://json-schema.org/draft-06/schema#",
    "type": "object",
    "properties": {
      "title": {
        "type": "string"
      },
      "title2": {
        "type": "string"
      },
      "iso_name": {
        "type": "string"
      },
      "orderof": {
        "type": "string"
      },
      "keynote": {
        "type": "string",
        "enum": [
          "C", "C#","Db", "D", "D#", "Eb", "E", "F", "F#", "Gb", "G", "G#", "Ab", "A", "A#", "Bb", "B","Cm", "C#m","Dbm", "Dm", "D#m", "Ebm", "Em", "Fm", "F#m", "Gbm", "Gm", "G#m", "Abm", "Am", "A#m", "Bbm", "Bm"
        ]
      },
      "tags": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "name": {
              "type": "string"
            },
            "iso_name": {
              "type": "string"
            }
          },
          "required": [
            "iso_name",
            "name"
          ]
        }
      },
      "authors": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "enum": [
                "Text",
                "Music",
                "Translated",
                "Copyright",
                "Artist"
              ]
            },
            "name": {
              "type": "string"
            }
          },
          "required": [
            "type",
            "name"
          ]
        }
      },
      "paragraphs": {
        "type": "array",
        "minItems": 1,
        "items": {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "enum": [
                "Verse",
                "Chorus",
                "Bridge"
              ]
            },
            "rows": {
              "type": "array",
              "minItems": 1,
              "items": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string"
                  },
                  "language-iso": {
                    "type": "string"
                  }
                },
                "required": [
                  "text",
                  "language-iso"
                ]
              }
            }
          },
          "required": [
            "type",
            "rows"
          ]
        }
      }
    },
    "required": [
      "title",
      "orderof",
      "keynote",
      "paragraphs",
      "iso_name"
    ]
}

Song change

To change a song in the database you must POST the following body to the BACKENDURL/song/change

And include the header Authorization mentioned in [Login]

Note that you don’t actually change anything with this call. You only make a request to change a song and how you want the new song to look.

However, the user will always be able to see his “Change Request” as a normal song but no one else will see it.

This is the same as the [Song add] body with the addition of the ’’‘“id2change”’’’ field. This id you must get from the [Song get] call where it is the “id” field.

{
   "id2change": 214,
   "title":"Awesome is our God",
   "orderof":"V1 V2",
   "keynote":"C",
   "iso_name":"en-US",
   "tags":[{"name": "psalmschanged","iso_name":"en-US"},{"name": "lovsong","iso_name":"en-US"}],
   "authors":[{"type":"Text", "name":"Bob"},{"type":"Music", "name":"John"}],
   "paragraphs":[
      {
         "type":"Verse",
         "rows":[
            {
               "text":"He is so awesome",
               "language-iso":"ar"
            },
            {
               "text":"that saved a wretch like me",
               "language-iso":"ar"
            }
         ]
      },
      {
         "type":"Verse",
         "rows":[
            {
               "text":"row 1",
               "language-iso":"ar"
            },
            {
               "text":"row 2",
               "language-iso":"ar"
            }
         ]
      }
   ]
}

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "id2change": {
      "type": "integer"
    },  
    "title": {
      "type": "string"
    },
    "title2": {
      "type": "string"
    },
    "iso_name": {
      "type": "string"
    },
    "orderof": {
      "type": "string"
    },
    "keynote": {
      "type": "string",
      "enum": [
        "C", "C#","Db", "D", "D#", "Eb", "E", "F", "F#", "Gb", "G", "G#", "Ab", "A", "A#", "Bb", "B","Cm", "C#m","Dbm", "Dm", "D#m", "Ebm", "Em", "Fm", "F#m", "Gbm", "Gm", "G#m", "Abm", "Am", "A#m", "Bbm", "Bm"
      ]
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "iso_name": {
            "type": "string"
          }
        },
        "required": [
          "iso_name",
          "name"
        ]
      }
    },
    "authors": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "Text",
              "Music",
              "Translated",
              "Copyright"
            ]
          },
          "name": {
            "type": "string"
          }
        },
        "required": [
          "type",
          "name"
        ]
      }
    },
    "paragraphs": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "Verse",
              "Chorus",
              "Bridge"
            ]
          },
          "rows": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "properties": {
                "text": {
                  "type": "string"
                },
                "language-iso": {
                  "type": "string"
                }
              },
              "required": [
                "text",
                "language-iso"
              ]
            }
          }
        },
        "required": [
          "type",
          "rows"
        ]
      }
    }
  },
  "required": [
    "title",
    "orderof",
    "keynote",
    "paragraphs",
    "iso_name",
    "id2change"
  ]
}

Song delete

To delete a song in the database you must POST the following body to the BACKENDURL/song/delete

And include the header Authorization mentioned in [Login]

Note that you don’t actually delete anything with this call. You only make a request to delete a song and give a reason why you want to delete it.

This song_id you must get from the [Song get] call where it is the “id” field.

{
   "song_id": 214
   "delete_reason":"This song is a duplicate of another song called xxxxx",
}

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "song_id": {
      "type": "integer"
    },    
    "delete_reason": {
      "type": "string"
    }
  },
  "required": [
    "song_id",
    "delete_reason"
  ]
}

Song get

To retreive songs from the database you must POST the following body to the BACKENDURL/song/get . NO AUTHORIZATION IS REQUIRED FOR THIS

Here is an example of the minimum body you can send.

{
    "how-many-per-page": 20,
    "page-number": 0,
    "returnfields":["title","title2","keynote"],
    "filters":[{"field":"iso_name", "like":"en-US"}]
}

Where here you specify how many records you want to get (how-many-per-page) and which page-number in the result you want to look at. Of course the first time you call this you want “page-number”: 0 . Here also is specified exactly what fields you want returned as show in the “returnfields”. The filter with at least ’iso_name IS REQUIRED.

There are 3 field definitions you can/need to send in the body. They are the following:

Field definition format example Description
returnfields array of fields [“title”,“title2”,“keynote”] The fields you want returned.
sort-by object{field,direction} “field”:“title” “direction”:“ASC” Allows you to sort on almost any field either ascending or descending. Sorting not allowed on Tag or Author
filters array[{field,like}] filters[{“field”:“title”, “like”:“thousand”}] A “like” operation is performed on the specified field with the specified text

There is also an “option-chords” you may include to choose if you want the chords inside of the texts or outside of the texts. The default is outside if this option is excluded. You need to put into the option-chords either IncludeInTexts or SeparateFromText. You can also choose “TextOnly” which will return the entire song in one text field without any chords.

The fields that can be returned/filtered/sorted are:

Field name Description
id This is a unique internal ID that probably is of no interest to the user. This is good to include in an overview so that if the user will see more then you just do a GetSong with filter-id= this id. This filter will never return more than one record
title The main title of the song. If used together with title2 then these two filtered are ‘or’ instead of ‘and’. CHANGED Any filter on title will automatically search title2 as well.
title2 A secondary title of the song. Often songs have several titles. CHANGED the title2 is ignored as a filter item. See “title”
text If this field is requested then all of the texts and chords of the song are returned. The name of the paragraph type is translated into the songs language, ie. Verse Chorus Bridge
iso_name This is the ISO name of the language of the song. Like ‘ar’ or ‘en-US’. You can also put here ‘ar-FA’ which would then do the searching in franco-arabic mode. That means that any filters(title,title2,text) must already be in franco-arabic in the call. All returned songs will be in arabic only. This field MUST be incuded in the search otherwise it will crash.
status The status of the song. Published, Submitted…
created The date the song was created
haschords 0 or 1. Zero if no chords. 1 if chords
orderof The order of the verses. Like V1 C1 V2 V3 V4. Where V=Verse and C=Chorus and B=Bridge
submitter The first name of the person who submitted the song.
publisher The first name of the person who published the song.
youtube_link Link to the song in youtube.
spotify_link Link to the song in spotify.
apple_music_link Link to the song in apple music.
anghami_link Link to the song in anghami.
sound_cloud_link Link to the song in Sound Cloud.
keynote The keynote of the chords in the song. This is different than all the other filters in that this can be a list of keynotes to match seperated by commas. For example C,Cm,C# . Do not put in any spaces or quotes in this string.
bibleverse Bible verses associated with the song. Right now the bibleverses are encoded with numbers. The format is Book,Chapter,FromVerse,ToVerse where Book is zero based number of the book in the bible. Chapter is the absolute chapter number from the beginning of the bible. Note that when returning these bible books, they are shown in the song language. But for filtering they are encoded and the search value must be encoded
Items below are not included in the sort
author_id When filtering, only one author id gotten from [[author get]] api. When returning it may be an array of author definitions gotten from a call to the [[author get]] api
tag_id When filtering, only one tag id gotten from [[tag get]] api. When returning it may be an array of tag definitions gotten from a call to the [[tag get]] api

Here is an example requesting all the returnfields and some filters, sorting and options in one search. Of course normally you would only use one or two filters at the same time but it is possible to use all of the filters. The result is an “AND” of all the filter results which means all of the filters must find a match in order to return something.

{
    "how-many-per-page": 20,
    "page-number": 0,
    "returnfields":["id", "title", "title2", "text", "iso_name", "status", "created", "author_id", "tag_id", "haschords", "submitter", "publisher", "keynote", "bibleverse","youtube_link","spotify_link","apple_music_link","anghami_link","sound_cloud_link"],
    "filters":[{"field":"title", "like":"hello"},{"field":"title2", "like":"hello"},{"field":"iso_name", "like":"en-US"}],
    "option-chords": "IncludeInTexts",
    "sort-by": {"field":"title","direction":"DESC"}
}

Here is a typical result of such a call:

{
    "status": 200,
    "total-count": 2,
    "page": 0,
    "data": [
        {
            "id": 18,
            "status": "Submitted",
            "title": "my first song",
            "title2": "my first song",
            "orderof": "V1 V2",
            "keynote": "C",
            "created": "2023-03-14 13:49:48",
            "updated": "2023-03-14 13:49:48",
            "iso_name": "en-US",
            "youtube_link": "",
            "spotify_link": null,
            "apple_music_link": "",
            "anghami_link": null,
            "sound_cloud_link": null,
            "tags": [
                {
                    "name": "Psalm",
                    "iso_name": "en-US"
                }
            ],
            "authors": [
                {
                    "name": "Bob",
                    "type": "Text"
                },
                {
                    "name": "John",
                    "type": "Music"
                }
            ],
            "paragraphs": [
                {
                    "type": "Verse",
                    "rows": [
                        {
                            "text": "Amazing grace how sweet the sound",
                             "chords": [
                                {
                                    "chord": "G",
                                    "position": 0
                                },
                                {
                                    "chord": "G",
                                    "position": 8
                                },
                                {
                                    "chord": "C",
                                    "position": 20
                                },
                                {
                                    "chord": "G",
                                    "position": 30
                                }
                            ]
                        },
                        {
                            "text": "row 2"
                        }
                    ]
                },
                {
                    "type": "Verse",
                    "rows": [
                        {
                            "text": "row 1"
                        },
                        {
                            "text": "row 2"
                        }
                    ]
                }
            ]
        },
        {
            "id": 19,
            "status": "Submitted",
            "title": "Amazing grace",
            "title2": null,
            "orderof": "V1 V2",
            "keynote": "C",
            "created": "2023-03-14 15:27:45",
            "updated": "2023-03-14 15:27:45",
            "iso_name": "en-US",
            "tags": [
                {
                    "name": "Psalm",
                    "iso_name": "en-US"
                }
            ],
            "authors": [
                {
                    "name": "Bob",
                    "type": "Text"
                },
                {
                    "name": "John",
                    "type": "Music"
                }
            ],
            "paragraphs": [
                {
                    "type": "Verse",
                    "rows": [
                        {
                            "text": "amazing grace how sweed the sound",
                        },
                        {
                            "text": "that saved a wretch like me",
                        }
                    ]
                },
                {
                    "type": "Verse",
                    "rows": [
                        {
                            "text": "row 1",
                        },
                        {
                            "text": "row 2",
                        }
                    ]
                }
            ]
        }
    ]
}

Note that “total-count” is not necessarily the number of records returned but it is the total number of results found. With this number you can count the total number of pages in the result = “total-count” divided by “how-many-per-page”. The actual number returned is limited by the call field “how-many-per-page”

Here is the json schema that defines the body of this REST API call.

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "how-many-per-page": {
      "type": "integer"
    },    
    "page-number": {
      "type": "integer"
    }, 
    "returnfields":{
      "type": "array",
      "items": {
          "type": "string",
          "enum": ["id","title","title2","text","iso_name","status","created","author_id","tag_id","haschords","orderof","submitter","publisher","keynote","bibleverse","youtube_link","spotify_link","apple_music_link","anghami_link","sound_cloud_link"]
      },
      "minItems": 1,
      "uniqueItems": true   
    },      
    "filters": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "field": {
            "type": "string",
            "enum": ["id","title","title2","text","iso_name","status","created","author_id","tag_id","haschords","orderof","submitter","publisher","keynote","bibleverse","youtube_link","spotify_link","apple_music_link","anghami_link","sound_cloud_link"]
          },
          "like": {
            "type": "string"
          }
        },
        "required": [
          "field",
          "like"
        ]    
      },
      "minItems": 1,
      "uniqueItems": true       
    },
    "option-chords": {
      "type": "string",
      "enum": [
        "IncludeInTexts",
        "SeparateFromText",
        "TextOnly"
      ]
    },
    "sort-by": {
      "type": "object",
      "properties": {
        "field": {
          "type": "string",
          "enum": ["id","title","title2","text","iso_name","status","created","haschords","orderof","submitter","publisher","keynote","bibleverse","youtube_link","spotify_link","apple_music_link","anghami_link","sound_cloud_link"]   
        },
        "direction": {
          "type": "string",
          "enum": [
            "ASC",
            "DESC"
          ]          
        }
      },
      "required": [
        "field",
        "direction"
      ] 
    }
  },
  "required": [
    "how-many-per-page",
    "page-number",
    "returnfields",
    "filters"
  ]
}

Song add Medley

To add a medley to the database you must POST the following body to the BACKENDURL/song/addmedley And include the header Authorization mentioned in [Login]

{
   "list-id":47,
   "notes":"This is a note for the set",
   "title":"Awesome is our God",
   "orderof":"V1 V2",
   "keynote":"C",
   "iso_name":"en-US",
   "tags":[{"name": "psalmschanged","iso_name":"en-US"},{"name": "lovsong","iso_name":"en-US"}],
   "authors":[{"type":"Text", "name":"Bob"},{"type":"Music", "name":"John"}],
   "paragraphs":[
      {
         "type":"Verse",
         "rows":[
            {
               "text":"He is so awesome",
               "language-iso":"ar"
            },
            {
               "text":"that saved a wretch like me",
               "language-iso":"ar"
            }
         ]
      },
      {
         "type":"Verse",
         "rows":[
            {
               "text":"row 1",
               "language-iso":"ar"
            },
            {
               "text":"row 2",
               "language-iso":"ar"
            }
         ]
      }
   ]
}

The list-id is the id of the users list that this medley will be added to. The notes is also for this medley in the users list.

Errors will be thrown as well in the following cases: * The rows.langauge-iso does not already exist in the database. * The rows.text has invalid brackets or invalid chords within []. Example [C#m] is acceptable. [Z] is not. * The orderof field must contain a valid ordering of the paragraphs. For instance, “V1 C1 V2 C1 B1”.

’‘’Orderof field’’’

The orderof field must contain a valid ordering of the paragraphs seperated by a space. Usually the Chorus will come several times in a song. With this field you must specify the order. “V” for Verse, “C” for Chorus and “B” for bridge. Bridge will sometimes show up at the end of a song. A typical song would be “V1 R1 V2 R1 V3 R1 B1”.

’‘’Allowed chords’’’

Any chord begining with a valid keynote will be accepted

’‘’Allowed keynote’’’

This program only accepts the following most common keynotes:

"C", "Cm", "C#","C#m","Db", "Dbm", "D", "Dm", "D#", "D#m", "Eb", "Ebm", "E", "Em", "F", "Fm", "F#", 
"F#m", "Gb", "Gbm", "G", "Gm", "G#", "G#m", "Ab", "Abm", "A", "Am", "A#", "A#m", "Bb", "Bbm", "B", "Bm"

The actual definition of how this json must look is the following json schema:

{
    "$schema": "http://json-schema.org/draft-06/schema#",
    "type": "object",
    "properties": {
      "list-id": {
        "type": "integer"
      }, 
      "notes": {
        "type": "string"
      },           
      "title": {
        "type": "string"
      },
      "title2": {
        "type": "string"
      },
      "iso_name": {
        "type": "string"
      },
      "orderof": {
        "type": "string"
      },
      "keynote": {
        "type": "string",
        "enum": [
          "C", "C#","Db", "D", "D#", "Eb", "E", "F", "F#", "Gb", "G", "G#", "Ab", "A", "A#", "Bb", "B","Cm", "C#m","Dbm", "Dm", "D#m", "Ebm", "Em", "Fm", "F#m", "Gbm", "Gm", "G#m", "Abm", "Am", "A#m", "Bbm", "Bm"
        ]
      },
      "tags": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "name": {
              "type": "string"
            },
            "iso_name": {
              "type": "string"
            }
          },
          "required": [
            "iso_name",
            "name"
          ]
        }
      },
      "authors": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "enum": [
                "Text",
                "Music",
                "Translated",
                "Copyright",
                "Artist"
              ]
            },
            "name": {
              "type": "string"
            }
          },
          "required": [
            "type",
            "name"
          ]
        }
      },
      "paragraphs": {
        "type": "array",
        "minItems": 1,
        "items": {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "enum": [
                "Verse",
                "Chorus",
                "Bridge"
              ]
            },
            "rows": {
              "type": "array",
              "minItems": 1,
              "items": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string"
                  },
                  "language-iso": {
                    "type": "string"
                  }
                },
                "required": [
                  "text",
                  "language-iso"
                ]
              }
            }
          },
          "required": [
            "type",
            "rows"
          ]
        }
      }
    },
    "required": [
      "list-id",
      "title",
      "orderof",
      "keynote",
      "paragraphs",
      "iso_name"
    ]
}

Song delete Medley

To delete a medley in the database (remove the medley completely) you must POST the following body to the BACKENDURL/song/deletemedley And include the header Authorization mentioned in [Login].

The person doing the deleting MUST be the one who created the medely

A typical call would look like the following

{
    "song_id":223
}

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "song_id": {
      "type": "integer"
    }
  },
  "required": [
    "song_id"
  ]
}

Song change Medley

To change a medley in the database you must POST the following body to the BACKENDURL/song/changemedley

And include the header Authorization mentioned in [Login]

This is the same as the [Song add] body with the addition of the ’’‘“id2change”’’’ field. This id you must get from the [Song get] call where it is the “id” field.

An example is the following

{
   "id2change": 214,
   "title":"Awesome is our God",
   "orderof":"V1 V2",
   "keynote":"C",
   "iso_name":"en-US",
   "tags":[{"name": "psalmschanged","iso_name":"en-US"},{"name": "lovsong","iso_name":"en-US"}],
   "authors":[{"type":"Text", "name":"Bob"},{"type":"Music", "name":"John"}],
   "paragraphs":[
      {
         "type":"Verse",
         "rows":[
            {
               "text":"He is so awesome",
               "language-iso":"ar"
            },
            {
               "text":"that saved a wretch like me",
               "language-iso":"ar"
            }
         ]
      },
      {
         "type":"Verse",
         "rows":[
            {
               "text":"row 1",
               "language-iso":"ar"
            },
            {
               "text":"row 2",
               "language-iso":"ar"
            }
         ]
      }
   ]
}

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "id2change": {
      "type": "integer"
    },  
    "title": {
      "type": "string"
    },
    "title2": {
      "type": "string"
    },
    "iso_name": {
      "type": "string"
    },
    "orderof": {
      "type": "string"
    },
    "keynote": {
      "type": "string",
      "enum": [
        "C", "C#","Db", "D", "D#", "Eb", "E", "F", "F#", "Gb", "G", "G#", "Ab", "A", "A#", "Bb", "B","Cm", "C#m","Dbm", "Dm", "D#m", "Ebm", "Em", "Fm", "F#m", "Gbm", "Gm", "G#m", "Abm", "Am", "A#m", "Bbm", "Bm"
      ]
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "iso_name": {
            "type": "string"
          }
        },
        "required": [
          "iso_name",
          "name"
        ]
      }
    },
    "authors": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "Text",
              "Music",
              "Translated",
              "Copyright"
            ]
          },
          "name": {
            "type": "string"
          }
        },
        "required": [
          "type",
          "name"
        ]
      }
    },
    "paragraphs": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "Verse",
              "Chorus",
              "Bridge"
            ]
          },
          "rows": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "properties": {
                "text": {
                  "type": "string"
                },
                "language-iso": {
                  "type": "string"
                }
              },
              "required": [
                "text",
                "language-iso"
              ]
            }
          }
        },
        "required": [
          "type",
          "rows"
        ]
      }
    }
  },
  "required": [
    "title",
    "orderof",
    "keynote",
    "paragraphs",
    "iso_name",
    "id2change"
  ]
}

Song insets

To retrieve a list of the most used songs that exist in the sets, you must GET/POST the following body to the BACKENDURL/song/insets. You do not need to include the header Authorization mentioned in [Login]

{
    "how-many-per-page": 3,
    "page-number": 0
}

And you will get something like the following in return

{
    "status": 200,
    "total-count": "3",
    "data": [
        {
            "id": 481,
            "count": 205,
            "title": "تسبيح للرب",
            "title2": "",
            "keynote": "Am",
            "language_id": 12
        },
        {
            "id": 1106,
            "count": 204,
            "title": "إلهنا عظيم إلهنا أمين",
            "title2": "يا الهنا الصالح شكرا ليك",
            "keynote": "Am",
            "language_id": 12
        },
        {
            "id": 243,
            "count": 200,
            "title": "أرفع يدي عاليًا لله",
            "title2": "",
            "keynote": "G",
            "language_id": 12
        }
    ]
}

Note that “total-count” is the number of records returned. The actual number returned is limited by the call field “how-many-per-page”

Where “count” is the number of times the song appears in a set.

Here is the json schema that defines the body of this REST API call.

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "how-many-per-page": {
      "type": "integer"
    },    
    "page-number": {
      "type": "integer"
    }
  },
  "required": [
    "how-many-per-page",
    "page-number"
  ]
}

Song getdeletes

To retrieve songs that have been requested to be deleted, you must in the database you must GET the following body to the BACKENDURL/song/get And include the header Authorization mentioned in [Login]

The person doing the get-deletes MUST be an Editor or greater.

{
    "how-many-per-page": 10,
    "page-number": 0
}

Here is a typical result of such a call:

{
    "status": 200,
    "total-count": 1,
    "page": 0,
    "data": [
        {
            "song_id": 28,
            "delete_id": 1,
            "delete_reason": "This song is a duplicate of another song called xxxxx",
            "title": "Awesome is our God",
            "title2": null,
            "submitter_id": 37,
            "status": "Submitted",
            "created": "2023-03-15 14:22:40"
        }
    ]
}

Note that “total-count” is not necessarily the number of records returned but it is the total number of results found. With this number you can count the total number of pages in the result = “total-count” divided by “how-many-per-page”. The actual number returned is limited by the call field “how-many-per-page”

Here is the json schema that defines the body of this REST API call.

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "how-many-per-page": {
      "type": "integer"
    },    
    "page-number": {
      "type": "integer"
    }
  },
  "required": [
    "how-many-per-page",
    "page-number"
  ]
}

Song autocomplete

To retrieve auto-completion to a song search string, you must GET/POST the following body to the BACKENDURL/song/autocomplete No header Authorization necessary.

The following is an example of such a call to get autocompletion

{
  "maximum-items": 5,
  "iso_name": "en-US",
  "titleortext": "text",
  "searchstring": "amasing grase ",
  "hit-prefix": "<span class=\"search-highlight\">",
  "hit-suffix": "</span>",
  "hit-text-length": 70
}

All of the above fields are required where:

  1. maximum-items is the maximum number of items you want retrieved.
  2. iso_name is the language you want to search in
  3. titleortext is either ‘title’ or ‘text’ to tell what field in the database to search. Text means the entire song text is searched
  4. searchstring is the string you want to be auto-completed
  5. hit-prefix is the text you want put in front of every match in the search. Most likely for highlighting the text
  6. hit-suffix is the text you want put after every match in the search to signal the end of the match.
  7. hit-text-length is approximately how many characters do you want to be in the string showing all the matches. This is only used when you search in the song “text” field. This is the length of the string returned MINUS all the prefixes and suffixes.When title searching it is always the entire title and title2.

Here is a typical result of such a call:

{
    "status": 200,
    "suggestions": [
        {
            "title": "Grace Flows Down",
            "hittexts": "<span class=\"search-highlight\">Amazing</span> <span class=\"search-highlight\">grace</span> how sweet the sound <span class=\"search-highlight\">Amazing</span> love now flowing down From hands",
            "id": 2347
        },
        {
            "title": "Amazing Grace",
            "hittexts": "<span class=\"search-highlight\">Amazing</span> <span class=\"search-highlight\">Grace</span> how sweet the sound that saved a wretch like me I once was",
            "id": 2151
        },
        {
            "title": "Broken Vessels",
            "hittexts": "<span class=\"search-highlight\">grace</span> How sweet the sound That saved a wretch like me I once was lost",
            "id": 2589
        },
        {
            "title": "These Thousand Hills",
            "hittexts": "love <span class=\"search-highlight\">amazing</span> <span class=\"search-highlight\">grace</span> Was on a hill my Savior died a broken heart and bleeding",
            "id": 2516
        },
        {
            "title": "Grace On Top Of Grace",
            "hittexts": "on top of <span class=\"search-highlight\">grace</span> Oooh Oh Oooh Oh Oooh Oh Oooh Oh Oooh Oh Oooh Oh Oooh <span class=\"search-highlight\">Amazing</span>",
            "id": 2894
        }
    ]
}

Here is the json schema that defines the body of this REST API call.

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
     "maximum-items": {
       "type": "integer"
      },
      "iso_name": {
        "type": "string"
      },
      "titleortext": {
          "type": "string",
          "enum": [
            "title", "text"
          ]
      },       
      "searchstring": {
        "type": "string"
      },
      "hit-prefix": {
        "type": "string"
      },
      "hit-suffix": {
        "type": "string"
      },
      "hit-text-length": {
        "type": "integer"
      }
  },
  "required": [
     "maximum-items",
     "iso_name",
     "titleortext",
     "searchstring",
     "hit-prefix",
     "hit-suffix",
     "hit-text-length"
  ] 
}

Song publish

To publish a song in the database you must POST the following body to the BACKENDURL/song/publish

And include the header Authorization mentioned in [Login]

The person doing the publishing MUST be an Editor or greater.

This will publish a “Submitted” or “Change Request”.

These ids are song ids. They are ids that you must get from the [Song get] call and filter-status=Submitted or ChangeRequest where it is the “id” field.

A typical call would look like the following

{
    "ids":[23,24]
}

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "ids": {
      "type": "array",
      "items": {
        "type": "integer"
      }      
    }
  },
  "required": [
    "ids"
  ]

}

Song reject

To reject a song in the database you must POST the following body to the BACKENDURL/song/reject

And include the header Authorization mentioned in [Login]

The person doing the rejecting MUST be an Editor or greater

These ids are song ids. They are ids that you must get from the [Song get] call and filter-status=Submitted or ChangeRequest where it is the “id” field.

A typical call would look like the following

{
    "ids":[23,24]
}

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "ids": {
      "type": "array",
      "items": {
        "type": "integer"
      }      
    }
  },
  "required": [
    "ids"
  ]
}

Song publish delete

To publish a delete-requested a song in the database (remove the song completely) you must POST the following body to the BACKENDURL/song/publishdelete And include the header Authorization mentioned in [Login].

The person doing the publishing MUST be an Editor or greater.

These ids ARE NOT SONG IDS. They are deleteRequest ids that you must get from the [Song getdeletes] call where it is the “delete_id” field.

A typical call would look like the following

{
    "ids":[23,24]
}

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "ids": {
      "type": "array",
      "items": {
        "type": "integer"
      }      
    }
  },
  "required": [
    "ids"
  ]
}

Song reject delete

To reject a delete-requested a song in the database (keep the song but remove the song delete request) you must POST the following body to the BACKENDURL/song/rejectdelete And include the header Authorization mentioned in [Login].

The person doing the rejecting MUST be an Editor or greater.

These ids ARE NOT SONG IDS. They are deleteRequest ids that you must get from the [Song getdeletes] call where it is the “delete_id” field.

A typical call would look like the following

{
    "ids":[23,24]
}

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "ids": {
      "type": "array",
      "items": {
        "type": "integer"
      }      
    }
  },
  "required": [
    "ids"
  ]
}

Song add request

To request to add a song in the database you must POST the following body to the BACKENDURL/song/addrequest And include the header Authorization mentioned in [Login].

To call this api, you need several text fields written by the user. The field song_request may be in any language BUT it must have the “\r\n” or simply “\n” characters to show the end of a line. Note that in the JSON, you must actually write the backslash with the “n” because json doesn’t allow the actual “\n” character.

A typical call would look like the following:

If the song-text-chords is give as a text string

{
    "song_request": "Verse 1\r\nA[C]mazing Grace how [F]sweet the [C]sound, that saved a wretch like [G]me\r\nI [C]once was lost but [F]now I am [C]found was blind\r\nbut [G]now I [C]see\r\nVerse 2\r\n`Twas [C]Grace that taught my [F]heart to [C]fear and grace my fears re[G]lieved",
    "writtenby":"Malek",
    "musicby":"Elias",
    "artist":"Thomas",
    "title":"Amazing Grace",
    "keynote":"C"
}

or if only the youtube link is given

{
    "writtenby":"Malek",
    "musicby":"Elias",
    "artist":"Thomas",
    "title":"Amazing Grace",
    "youTubeLink":"https://www.youtube.com/watch?v=fNE_XLdlfT8",
    "keynote":"C"
}

or if only one or more files are given in the BASE64 format

{
    "writtenby":"Malek",
    "musicby":"Elias",
    "artist":"Thomas",
    "title":"Amazing Grace",
    "files":[
      {
        "file":"/9j/4AAQSkZJRgABAQEBLAEsAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/4gKwSUNDX1BST0ZJTEUAAQEAAAKgbG...continued as a very very long text string",
        "filename":"pictureGrace1.jpg"
      },
      {
        "file":"/9j/4AAQSkZJRgABAQEBLAEsAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/4gKwSUNDX1BST0ZJTEUAAQEAAAKgbG...continued as a very very long text string",
        "filename":"pictureGrace2.jpg"
      }
    ],
    "keynote":"C"
}

You may if you wish give both a file and a url and a text. But only one of these 3 are required. The title is also required.

A postitive return will look like the following:

{
    "status": 200,
    "message": "See your email confirmation of the add request"
}

The user will then be sent a confirmation email that looks like the following:

Hello thomas.dilts60@gmail.com,

Thank you for submitting the following song request. We will notify you when we have added it.

Title: Amazing Grace

Written by: Malek

Music: Elias

Artist: Thomas

YouTube link: https://www.youtube.com/watch?v=fNE_XLdlfT8

Keynote: C

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "title": {
      "type": "string"
    },  
    "writtenby": {
      "type": "string"
    },
    "musicby": {
      "type": "string"
    },
    "artist": {
      "type": "string"
    },
    "youTubeLink": {
      "type": "string"
    },
    "files": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "file": {
            "type": "string"
          },
          "filename": {
            "type": "string"
          }
        },
        "required": ["file","filename"]
      }
    },
    "song_request": {
      "type": "string"
    },
    "keynote": {
      "type": "string",
      "enum": [
        "C", "C#","Db", "D", "D#", "Eb", "E", "F", "F#", "Gb", "G", "G#", "Ab", "A", "A#", "Bb", "B","Cm", "C#m","Dbm", "Dm", "D#m", "Ebm", "Em", "Fm", "F#m", "Gbm", "Gm", "G#m", "Abm", "Am", "A#m", "Bbm", "Bm"
      ]
    }
  },
  "required": ["title"],
  "anyOf": [
    { "required": ["youTubeLink"] },
    { "required": ["files"] },
    { "required": ["song_request"] }
  ]
}

Song error report

To report an error in a song in the database you must POST the following body to the BACKENDURL/song/errorreport And include the header Authorization mentioned in [Login].

To call this api, you need the song id that the error is reported on (that you must get from the [Song get] call) and the error text written by the user. This text may be in any language BUT it must have the “\r\n” or simply “\n” characters to show the end of a line. Note that in the JSON, you must actually write the backslash with the “n” because json doesn’t allow the actual “\n” character.

A typical call would look like the following

{
    "songid": 2015,
    "errortext": "This is an error report from the user.\r\nNote that this text is with the carrige-return/line-feed shown explicitly in the text\r\n"
}

A postitive return will look like the following:

{
    "status": 200,
    "message": "See your email confirmation of the error report"
}

The user will then be sent a confirmation email that looks like the following which has his message att the bottom of the email:

Hello thomas@jesusislord.se,

Thank you for submitting the following song error report. We will notify you when we have made the corrections.

This is an error report from the user.
Note that this text is with the carrige-return/line-feed shown explicitly in the text

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "songid": {
      "type": "integer"
    },  
    "errortext": {
      "type": "string"
    },
  },
  "required": [
    "songid",
    "errortext"
  ]
}

Tag

Tag-get

To retrieve a list of available tags, you must in the database you must GET the following body to the BACKENDURL/tag/get

No Authorization

{
    "how-many-per-page": 20,
    "page-number": 0,
    "filters":[{"field":"name", "like":"Country"},{"field":"iso_name", "like":"en-US"}]
    "sort-by":{"field":"name", "direction":"ASC"}
}

Here is a typical result of such a call:

{
    "status": 200,
    "total-count": 1,
    "page": 0,
    "data": [
        {
            "id": 4,
            "name": "Country",
            "firstname": "Thomas",
            "iso_name": "en-US",
            "status": "Published",
            "created": "2023-03-14 13:49:48"
        }
    ]
}

Note that “total-count” is not necessarily the number of records returned but it is the total number of results found. With this number you can count the total number of pages in the result = “total-count” divided by “how-many-per-page”. The actual number returned is limited by the call field “how-many-per-page”

Here is the json schema that defines the body of this REST API call.

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "how-many-per-page": {
      "type": "integer"
    },    
    "page-number": {
      "type": "integer"
    },
    "filters": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "field": {
            "type": "string",
            "enum": ["id","name","iso_name","created","status","firstname"]
          },
          "like": {
            "type": "string"
          }
        },
        "required": [
          "field",
          "like"
        ]    
      },
      "minItems": 1,
      "uniqueItems": true       
    },
    "sort-by": {
      "type": "object",
      "properties": {
        "field": {
          "type": "string",
          "enum": ["id","name","iso_name","created","status","firstname"]   
        },
        "direction": {
          "type": "string",
          "enum": [
            "ASC",
            "DESC"
          ]          
        }
      },
      "required": [
        "field",
        "direction"
      ] 
    }
  },
  "required": [
    "how-many-per-page",
    "page-number"
  ]
}

Tutorial

Tutorial-get

To retrieve a list of available tutorials, you must in the database you must POST the following body to the BACKENDURL/tutorial/get

No Authorization

{
  "iso_name":"en-US",
  "category":"Guitar",
  "level":"Beginner"
}

They will be sorted by their position. Note that this does NOT return the thumbnail for the tutorial. To get that you must call [Tutorial-thumbnail] with the id from the result of this call. Here is a typical result of such a call:

{
    "status": 200,
    "total-count": 3,
    "data": [
        {
            "id": 4,
            "title": "Lesson 1",
            "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum",
            "language_id": 3,
            "category": "Guitar",
            "level": "Beginner",
            "link": "",
            "embedded_link": "https://www.youtube.com/embed/LioSo-fKAOo?si=JiyIZQ506vwO-yj4",
            "position": 0,
            "image_url": "https://jesusislord.se/zamar/backend/web/../../fileVault/1AjkVeqZdiyeGw2XZEAzmrXa4Go4FT"
        },
        {
            "id": 13,
            "title": "This should be second",
            "description": "sdfsafdsadfsadfasfdas",
            "language_id": 3,
            "category": "Guitar",
            "level": "Beginner",
            "link": "https://www.youtube.com/watch?v=xSTELdXdC-w",
            "embedded_link": "",
            "position": 1,
            "image_url": "https://jesusislord.se/zamar/backend/web/../../fileVault/1AjkVeqZdiyeGw2XZEAzmrXa4Go4FT"
        },
        {
            "id": 14,
            "title": "this should be third",
            "description": "asdfsadf sfsad f sad f sadf sa df asd",
            "language_id": 3,
            "category": "Guitar",
            "level": "Beginner",
            "link": "",
            "embedded_link": "https://www.youtube.com/embed/LioSo-fKAOo?si=JiyIZQ506vwO-yj4",
            "position": 2,
            "image_url": "https://jesusislord.se/zamar/backend/web/../../fileVault/1AjkVeqZdiyeGw2XZEAzmrXa4Go4FT"
        }
    ]
}

The idea behind this is that the embedded link is for a src to an iframe in a webpage. If that is not useable by you then a normal link and a thumbnail could be used instead. Then you would neet to call [Tutorial-thumbnail]

Here is the json schema that defines the body of this REST API call.

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "iso_name": {
      "type": "string",
      "enum": ["ar", "en-US"]
    },  
    "category": {
      "type": "string",
      "enum": ["Guitar","Piano","Singing","Devotion"]
    },
    "level": {
      "type": "string",
      "enum": ["Beginner","Intermediate","Advanced"]
    }     
  },
  "required": [
    "iso_name",
    "category",
    "level"
  ]
}

Tutorial-thumbnail

To retrieve a thumbnail for a tutorial, you must POST the following body to the BACKENDURL/tutorial/thumbnail

No Authorization

{
  "id":4
}

Where “id” is a valid id of a tutorial gotten from the call to [Tutorial-get]. The following is a typical result of such a call.

{
    "status": 200,
    "data": 
        {
            "id": 13,
            "title": "This should be second",
            "description": "sdfsafdsadfsadfasfdas",
            "language_id": 3,
            "category": "Guitar",
            "level": "Beginner",
            "link": "https://www.youtube.com/watch?v=xSTELdXdC-w",
            "embedded_link": "",
            "position": 1,
            "image_url": "https://jesusislord.se/zamar/backend/web/../../fileVault/1AjkVeqZdiyeGw2XZEAzmrXa4Go4FT",
            "image": "data:image/jpg;base64,iVBORw0KGgoAAAANSUhEUgAAAkAAAAIFCAYAAAA6D0LpAAAABHNCSVQICAgIfAhkiAAAAB this can be extremely long"
        }
}

The ‘image’ field might be a blank string or null.

The idea behind this is that the embedded link is for a src to an iframe in a webpage. If that is not useable by you then a normal link and an image (thumnbnail) could be used instead.

Here is the json schema that defines the body of this REST API call.

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "id": {
      "type": "integer"
    }
  },
  "required": [
    "id"
  ]
}

User

User get

To retrieve a list of users, you must in the database you must GET the following body to the BACKENDURL/user/get And include the header Authorization mentioned in [Login]

You must be an Administrator to be allowed to call this API

{
    "how-many-per-page": 20,
    "page-number": 0,
    "filter-username": "thomas",
    "filter-email": "thomas",
    "filter-role": "Editor",
    "sort-by":{"field":"username", "direction":"ASC"}
}

The ‘filter-username’ checks both the firstname and lastname.

Here is a typical result of such a call:

{
    "status": 200,
    "total-count": 1,
    "page": 0,
    "data": [
        {
            "id": 37,
            "role": "Editor",
            "status": 10,
            "created_at": "2023-03-13 23:14:17",
            "updated_at": "2023-03-16 17:00:35",
            "firstname": "thomas",
            "lastname": "dilts",
            "email": "thomas@jesusislord.se",
            "email_awaiting": null,
            "language": "ar"
        }
    ]
}

Note that “total-count” is not necessarily the number of records returned but it is the total number of results found. With this number you can count the total number of pages in the result = “total-count” divided by “how-many-per-page”. The actual number returned is limited by the call field “how-many-per-page”

Note that there is a logic behind the fields “email” and “email_awaiting.” If email_awaiting is not null then you may not change the field “email” with a call to [user profile change]. Or you can call [user email awaiting delete] to delete the email_awaiting if desired. Email_awaiting will be filled with the new email that is awaiting verification. You can also call [[resend-verification-email]] to once again send the email_awaiting verification.

Here is the json schema that defines the body of this REST API call.

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "how-many-per-page": {
      "type": "integer"
    },    
    "page-number": {
      "type": "integer"
    },
    "filter-username": {
      "type": "string"
    },
    "filter-email": {
      "type": "string"
    },
    "filter-role": {
      "type": "string"
    },        
    "filter-date-between": {
      "type": "object",
      "properties": {
        "fromdate": {
          "type": "string"
        },
        "todate": {
          "type": "string"
        }
      },
      "required": [
        "fromdate",
        "todate"
      ] 
    },
    "sort-by": {
      "type": "object",
      "properties": {
        "field": {
          "type": "string",
          "enum": [
            "username",
            "email",
            "role",
            "created_at"
          ]          
        },
        "direction": {
          "type": "string",
          "enum": [
            "ASC",
            "DESC"
          ]          
        }
      },
      "required": [
        "field",
        "direction"
      ] 
    }
  },
  "required": [
    "how-many-per-page",
    "page-number"
  ]
}

User email awaiting delete

To remove or nullify the email_awaiting field, you must GET to the BACKENDURL/user/emailawaitingdelete

And include the header Authorization mentioned in [Login]

There is NO BODY in this call.

Here is a typical result of such a call:

{
    "status": 200
}

User profile get

To retrieve the logged in users profile information, you must GET to the BACKENDURL/user/profileget

And include the header Authorization mentioned in [Login]

This is the only API where you can send with a body or without. This is done for backwards compatibility. If the body is present then this profileget can return ‘ar-FA’ in the field langaugesong. Without this body then only ‘ar’ and ‘en-US’ can be returned in the field languagesong.

The body would in that case look like this

{
    "allow-franco-arabic":true
}

Here is a typical result of such a call:

{
    "status": 200,
    "data": {
        "id": 73,
        "role": "Member",
        "status": 10,
        "last_notification_date": "2023-04-25 19:09:20",
        "created_at": "2023-04-25 19:09:20",
        "updated_at": "2023-08-07 12:18:12",
        "firstname": "Thomas",
        "lastname": "Dilts",
        "email": "thomas.dilts60@gmail.com",
        "email_awaiting": null,
        "newsletter": true,
        "languagegui": "en-US",
        "languagesong": "ar-FA",
        "photo": "data:image/jpg;base64,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"
    }
}

Where last_notification_id is the last notification seen by the user.

Note that the field “photo” will not be present if the user has no personalized photo.

Note that there is a logic behind the fields “email” and “email_awaiting.” If email_awaiting is not null then you may not change the field “email” with a call to [user profile change]. Or you can call [user email awaiting delete] to delete the email_awaiting if desired. Email_awaiting will be filled with the new email that is awaiting verification. You can also call [resend verification email] to once again send the email_awaiting verification.

Here is the json schema that defines the body of this REST API call. Again, this body is optional for backwards compatibilities sake.

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "allow-franco-arabic": {
      "type": "boolean"
    }            
  },
  "required": [
    "allow-franco-arabic"
  ]
}

User profile delete

To delete the logged in users profile information and account, you must POST to the BACKENDURL/user/profiledelete

And include the header Authorization mentioned in [Login]

There is NO BODY in this call.

After this call the user is completely removed from the system and can no longer log in.

User profile change

A user can change many fields in their profile. To do so POST the following body to the BACKENDURL/user/profilechange

And include the header Authorization mentioned in [Login]

An example of the body to be sent is as follows

{
    "languagegui": "en-US",
    "songlanguage": "en-US",
    "firstname": "thomas",
    "lastname": "Dilts",
    "email": "thomas.dilts60@gmail.com",
    "password": "HeLovedUsFirst123!",
    "last_notification_date": "2023-04-25 19:09:20",
    "songrole": "Guitar",
    "newsletter": true,
    "photo": "data:image/jpg;base64,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"
}

Note that there is a logic behind the fields “email” and “email_awaiting.” If email_awaiting is not null then you may not change the field “email” with a call to [user profile change]. Or you can call [user email awaiting delete] to delete the email_awaiting if desired. Email_awaiting will be filled with the new email that is awaiting verification. You can also call [resend verification email] to once again send the email_awaiting verification.

You may not directly change “email_awaiting” but by changing “email” then the “email” will remain the same as before but the “email_awaiting” will get the new email value and it will keep that value until the email is verified. Also a verification email will automatically be sent when email is changed.

ONLY include fields that you want to change!

If you add the photo field and set it to null as in the following example, then any existing personalized photo will be erased

{
    "languagegui": "en-US",
    "songlanguage": "en-US",
    "firstname": "thomas",
    "lastname": "Dilts",
    "email": "thomas.dilts60@gmail.com",
    "password": "HeLovedUsFirst123!",
    "last_notification_date": "2023-04-25 19:09:20",
    "songrole": "Guitar",
    "newsletter": true,
    "photo": null
}

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "languagegui": {
      "type": "string"
    },
    "songlanguage": {
      "type": "string"
    },
    "firstname": {
      "type": "string"
    },
    "lastname": {
      "type": "string"
    },
    "last_notification_id": {
      "type": "string"
    },
    "email": {
      "type": "string"
    },
    "password": {
      "type": "string"
    },
    "newsletter": {
      "type": "boolean"
    },
    "songrole": {
      "type": "string",
      "enum": [
        "Guitar","Ukulele","Piano","Singer"
      ]
    },    
    "photo": {
      "type": ["string","null"]
    },
    "country": {
      "type": "string"
    },
    "birthyear": {
      "type": "integer"
    },
    "gender": {
      "type": "string",
      "enum": [
        "Male",
        "Female"
      ]
    }                 
  }
}

User change

Only an Administrator can change another users profile information. To do so POST the following body to the BACKENDURL/user/change

And include the header Authorization mentioned in [Login]

The id is gotten from the call to [User get]

{
    "id": "37",
    "firstname": "thomas",
    "lastname": "dilts",
    "password": "12345678",
    "email": "thomas@jesusislord.se",
    "role": "Administrator",
    "language-iso": "en-US"
}

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    },
    "firstname": {
      "type": "string"
    },
    "lastname": {
      "type": "string"
    },
    "email": {
      "type": "string"
    },
    "password": {
      "type": "string"
    },
    "language-iso": {
      "type": "string"
    },
    "role": {
      "type": "string",
      "enum": [
        "Administrator",
        "Editor",
        "Member"
      ]      
    }
  },
  "required": [
    "id"
  ]
}

User delete

Only an Administrator can delete another users profile/account. To do so POST the following body to the BACKENDURL/user/delete

And include the header Authorization mentioned in [Login]

The ids are gotten from the call to [User get] and the id field

{
    "ids": [22,34,45]
}

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "ids": {
      "type": "array",
      "items": {
        "type": "integer"
      }      
    }
  },
  "required": [
    "ids"
  ]
}

Statistics

Statistics add

To add a new statistics entry to the database you must POST the following body to the BACKENDURL/statistics/add

NO AUTHORIZATION IS REQUIRED FOR THIS - both authenticated and anonymous users can add statistics entries. HOWEVER if you know who the user is then you MUST include a valid user_id for statistical reasons.

With this API you can add statistics about any of the following:

  1. Song usage.
  2. page usage. Can also be combined with any of the other usages to achieve more fine tuned statistics if desired. This is simply a string id of the particular page in the app that the statistics is being gathered on.
  3. autocomplete searches. Use the search_texts and song_language_

If you want to track the users total time logged in then use the page_name “user logged in time” and of course include the user_id or send the bearer token.

Below is an example of a statistics addition:

{
    "seconds_observed": 45,
    "page_name": "library/view",
    "song_id": 123
}

The seconds_observed field is required(but can be zero if needed), and at least one of the following fields must be provided: page_name or song_id.

Field Descriptions:

  • seconds_observed (required): Number of seconds the user spent on the page/activity (must be 0 or greater)
  • page_name (conditional): Name of the page or activity being tracked (max 255 characters)
  • song_id (conditional): ID of the song being viewed/interacted with
  • search_texts (optional): Only used when recording the [[song-autocomplete]]

Notes:

  • If the user is authenticated, their user_id will be automatically recorded
  • If the user is anonymous, user_id will be stored as null
  • Foreign key references (song_id, search_language_id) is validated against existing records
  • Invalid foreign key references will result in validation errors

Required statistics to gather (X denotes used fields)

Description page_name seconds_observed song_id search_texts
When a single song is viewed in the library app/library/song X X
When a single song is viewed in the sets app/sets/song X X
When chord families are viewed app/chords/chordfamily X
When chords are viewed app/chords/chords X
When the tunner is viewed app/tuner X
When Autocomplete search on titles is used and a song is selected app/sets/song/title X (always 0) X X
When Autocomplete search on texts is used and a song is selected app/sets/song/text X (always 0) X X

The typical successful response is:

{
    "status": 200,
    "message": "Statistics entry created successfully",
    "data": {
        "id": 456,
        "created": "2024-01-15 14:30:22",
        "song_id": 123,
        "seconds_observed": 45,
        "page_name": "library/view",
    }
}

The actual definition of how this json must look is the following json schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "seconds_observed": {
      "type": "integer",
      "minimum": 0
    },
    "page_name": {
      "type": "string",
      "maxLength": 255
    },
    "song_id": {
      "type": "integer",
      "minimum": 1
    },
    "search_texts": {
      "type": "string",
      "maxLength": 255
    }
  },
  "required": [
    "seconds_observed",
    "page_name"
  ]
} 

Statistics get

To retrieve statistics for the current authenticated user you must GET to the BACKENDURL/statistics/get

AUTHORIZATION IS REQUIRED - only authenticated users can view their own statistics.

Query parameters (all optional):

  • song_id: Filter by specific song ID
  • tag_id: Filter by specific tag ID
  • author_id: Filter by specific author ID
  • page_name: Filter by page name (exact match)
  • search_texts: Filter by search text (partial match)
  • keynote_search: Filter by keynote (exact match)
  • search_language_id: Filter by search language ID (exact match)
  • per-page: Number of results per page (default 20)
  • page: Page number (0-based)

Example URL: BACKENDURL/statistics/get?song_id=123&per-page=10&page=0

The typical response is:

{
    "status": 200,
    "data": [
        {
            "id": 456,
            "created": "2024-01-15 14:30:22",
            "song_id": 123,
            "seconds_observed": 45,
            "page_name": "library/view",
            "tag_id": 5,
            "author_id": 12,
            "song": {
                "id": 123,
                "title": "Amazing Grace",
                "keynote": "C"
            },
            "tag": {
                "id": 5,
                "name": "Hymns"
            },
            "author": {
                "id": 12,
                "name": "John Newton",
                "type": "Text"
            }
        }
    ],
    "total-count": 1,
    "page-count": 1,
    "current-page": 1
}

Statistics admin-get

To retrieve all statistics from all users (admin only) you must GET to the BACKENDURL/statistics/admin-get

AUTHORIZATION IS REQUIRED - only users with roles ‘theCreator’, ‘Administrator’, or ‘Editor’ can access this endpoint.

Query parameters (all optional):

  • user_id: Filter by specific user ID
  • song_id: Filter by specific song ID
  • tag_id: Filter by specific tag ID
  • author_id: Filter by specific author ID
  • page_name: Filter by page name (exact match)
  • search_texts: Filter by search text (partial match)
  • keynote_search: Filter by keynote (exact match)
  • search_language_id: Filter by search language ID (exact match)
  • per-page: Number of results per page (default 20)
  • page: Page number (0-based)

Example URL: BACKENDURL/statistics/admin-get?user_id=789&per-page=20&page=0

The typical response is:

{
    "status": 200,
    "data": [
        {
            "id": 456,
            "created": "2024-01-15 14:30:22",
            "user_id": 789,
            "song_id": 123,
            "seconds_observed": 45,
            "page_name": "library/view",
            "tag_id": 5,
            "author_id": 12,
            "search_texts": "amazing grace hymn",
            "keynote_search": "C",
            "search_language_id": 12,
            "user": {
                "id": 789,
                "firstname": "John",
                "lastname": "Doe",
                "email": "john@example.com"
            },
            "song": {
                "id": 123,
                "title": "Amazing Grace",
                "keynote": "C"
            },
            "tag": {
                "id": 5,
                "name": "Hymns"
            },
            "author": {
                "id": 12,
                "name": "John Newton",
                "type": "Text"
            }
        }
    ],
    "total-count": 1,
    "page-count": 1,
    "current-page": 1
}

Notes:

  • Anonymous user entries will show user_id as null and no user object
  • Results include full user information for admin visibility

Statistics summary

To retrieve analytics summary for the current authenticated user you must GET to the BACKENDURL/statistics/summary

AUTHORIZATION IS REQUIRED - only authenticated users can view their own summary.

No query parameters are required.

The typical response is:

{
    "status": 200,
    "data": {
        "total_time_seconds": 3600,
        "total_entries": 45,
        "top_songs": [
            {
                "song_id": 123,
                "total_seconds": 450,
                "song": {
                    "id": 123,
                    "title": "Amazing Grace",
                    "keynote": "C"
                }
            },
            {
                "song_id": 124,
                "total_seconds": 320,
                "song": {
                    "id": 124,
                    "title": "How Great Thou Art",
                    "keynote": "G"
                }
            }
        ],
        "page_statistics": [
            {
                "page_name": "library/view",
                "total_seconds": 1200,
                "visits": 15
            },
            {
                "page_name": "set/view",
                "total_seconds": 800,
                "visits": 10
            }
        ]
    }
}

Summary Data Includes:

  • total_time_seconds: Total time spent across all activities
  • total_entries: Total number of statistics entries
  • top_songs: Top 10 most viewed songs with total time spent
  • page_statistics: Time spent and visit counts per page, ordered by total time

Error Responses

All endpoints return appropriate HTTP status codes and error messages:

400 Bad Request

  • Invalid request body or parameters:
{
    "status": 400,
    "message": "seconds_observed is required"
}

401 Unauthorized

  • Authentication required:
{
    "status": 401,
    "message": "Your request was made with invalid credentials."
}

403 Forbidden

  • Insufficient permissions:
{
    "status": 403,
    "message": "You are not allowed to perform this action."
}

422 Unprocessable Entity

  • JSON schema validation failed:
{
    "status": 422,
    "message": "The given data was invalid.",
    "errors": ["seconds_observed must be an integer"]
}

Usage Examples

Track page visit time:

POST /statistics/add
{
    "seconds_observed": 120,
    "page_name": "library/index"
}

Track song interaction:

POST /statistics/add
{
    "seconds_observed": 180,
    "page_name": "library/view",
    "song_id": 123
}

Track filtered search:

POST /statistics/add
{
    "seconds_observed": 45,
    "page_name": "library/search",
    "tag_id": 5,
    "author_id": 12
}

Track search with text and keynote:

POST /statistics/add
{
    "seconds_observed": 30,
    "page_name": "library/search",
    "search_texts": "amazing grace",
    "keynote_search": "C"
}

Track search with language filter:

POST /statistics/add
{
    "seconds_observed": 45,
    "page_name": "library/search",
    "search_texts": "يا رب",
    "search_language_id": 12
}

Get user’s song-specific statistics:

GET /statistics/get?song_id=123

Admin view of all statistics for a specific user:

GET /statistics/admin-get?user_id=789&per-page=50

Filter statistics by search language:

GET /statistics/get?search_language_id=12