I am using retrofit and I have a post request
interface NetworkApi {
#Headers("Content-Type: application/json")
#POST("/")
fun startLoginRequest(#Body loginModel : LoginModel) : Call<BaseResponseModel>
class Factory {
var retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
companion object{
fun getApi (): NetworkApi {
val serverApi = NetworkApi.Factory()
val api = serverApi.retrofit.create(NetworkApi::class.java)
return api
}
}
}
}
when I use this method and on response method were calling response body is always null.
fun startLoginRequest(){
val api = NetworkApi.Factory.getApi()
val request = api.startLoginRequest(loginRequestModel)
request.enqueue(object : Callback<BaseResponseModel>{
override fun onFailure(call: Call<BaseResponseModel>?, t: Throwable?) {
}
override fun onResponse(call: Call<BaseResponseModel>?, response: Response<BaseResponseModel>?) {
//here response.body is null
}
})
}
Strange thing is that, if I will send the same object using Postman everything works fine
this is httpClient interceptor log
--> POST http://myExampleHost.net/ http/1.1
Content-Type: application/json
Content-Length: 95
Authorization: auth-value
--> END POST (95-byte body)
<-- 405 Method Not Allowed http://myExampleHost.net/ (198ms)
Allow: GET, HEAD, OPTIONS, TRACE, COPY, PROPFIND, LOCK, UNLOCK
Content-Type: text/html
Server: Microsoft-IIS/7.5
X-Powered-By: ASP.NET
X-Powered-By-Plesk: PleskWin
Date: Thu, 17 Nov 2016 08:04:57 GMT
Content-Length: 1107
<HTML>
<HEAD>
<TITLE>405 Method Not Allowed</TITLE>
<BASE href="/error_docs/"><!--[if lte IE 6]></BASE><![endif]-->
</HEAD>
<BODY>
<H1>Method Not Allowed</H1>
The HTTP verb used to access this page is not allowed.<P>
<HR>
<ADDRESS>
Web Server at example address
</ADDRESS>
</BODY>
</HTML>
For now I'm using OkHttp RequestBuilder and everything works fine, I don't realize what I'm missing in the above example
val client = OkHttpClient()
val JSON = MediaType.parse("application/json; charset=utf-8")
val body = RequestBody.create(JSON,json)
val request1 = Request.Builder()
.url("http:example.com")
.addHeader("content-type", "application/json")
.method("POST", body)
.build()
client.newCall(request1).enqueue(object : okhttp3.Callback{
override fun onFailure(call: okhttp3.Call?, e: IOException?) {
}
override fun onResponse(call: okhttp3.Call?, response: okhttp3.Response?) {
response?.body()?.string()?.print("RETROFIT response")
}
})
My best guess is that you are trying to post to the wrong url www.example.com/login// instead of www.example.com/login/. APIs can be a bit finicky about posts.
You can add the logging interceptor so you can see what you are posting and to what URL so its a little easier to debug. To set it up you need to add compile "com.squareup.okhttp3:logging-interceptor:$okhttp_version" to your gradle file and change your retrofit setup to something like this:
val httpClient = OkHttpClient.Builder().apply {
if (BuildConfig.DEBUG) {
httpClient.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
}
}
var retrofit = Retrofit.Builder()
.client(httpClient.build())
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
That's the best advice I can give without seeing the error logs.
For me the main reason was i was calling http instead https
For Example:
i was using this url as a base url which give me this error
http://www.facebook.com/
when i changed it to this one it worked like a charm
https://www.facebook.com/
I was missing "/" at the end of my url in the POST request.
Changing url from
www.example.com/list_products
to www.example.com/list_products/ worked for me.
Method not allowed error with status code-405 is showed when we are not sending our request method or wrong request method is getting sent.
In my scenario,Api is of POST type and I was checking for GET request type,
So try change your proper Request method(GET/POST/PUT/DELETE).
I found the answer, the problem was url, my baseUrl were http//www.example.com/url1/url2/...../service
and #POST("/")
when I saw the POST endpoint it was http//www.example.com/ and I don't know why
I just changed this urls
baseUrl = http//www.example.com/
and #POST("url1/url2/...../service")
Related
I am working on an user app for a local charitable organization, and need to access their API. The API is from wild apricot, and this is the documentation for making a token request:
Authentication tokens are obtained from Wild Apricot's authentication service, which is located at https://oauth.wildapricot.org. This service adheres to oAuth 2.0.
This is the access option I need to implement:
-----------------In order to obtain access token with API key, you have to make the following request:
POST /auth/token HTTP/1.1
Host: oauth.wildapricot.org
Authorization: Basic BASE64_ENCODED("APIKEY:YOUR_API_KEY")
Content-type: application/x-www-form-urlencoded
grant_type=client_credentials&scope=auto
-------------------------------So. finally your request will look like:
POST /auth/token HTTP/1.1
Host: oauth.wildapricot.org
Authorization: Basic QVBJS0VZOm85c2U4N3Jnb2l5c29lcjk4MDcwOS0=
Content-type: application/x-www-form-urlencoded
grant_type=client_credentials&scope=auto
I am attempting to make this call with retrofit2, and an okhttp3 interceptor, and getting a bad request response (I am very much new and learning, and have not been able to get anything other response than a 400 bad request (when I use "/auth/token" as the endpoint), or a 404 not found (when I use "/auth/token HTTP/1.1" as the endpoint). If someone could tell me where exactly I am messing this up It would be greatly appreciated, the code I have tried is below.
Interface:
interface WAApiCall {
#POST("auth/token")
fun callPost(#Body body:String ): Call<AuthToken>
}
Call Service:
object WAApiCallService {
private const val API_KEY = "xxxxxxxxIxHavexAxValidxKeyxxxx"
private const val BASE_URL = "https://oauth.wildapricot.org/"
private val AUTH = "Basic" + Base64.encodeToString(API_KEY.toByteArray(), Base64.NO_WRAP)
private const val CONTENT_TYPE = "application/x-www-form-urlencoded"
private var api:WAApiCall? = null
private fun getWAApi(context: Context) : WAApiCall {
if(api==null){
val OkHttpClient = OkHttpClient.Builder()
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.BASIC
OkHttpClient.addInterceptor{chain ->
val request = chain.request()
Log.d("CALL", request.body.toString())
val newRequest = request.newBuilder()
.addHeader("Host", "oauth.wildapricot.org")
.addHeader("Authorization", AUTH )
.addHeader("Content-type", CONTENT_TYPE)
.method(request.method, request.body)
.build()
chain.proceed(newRequest)
}
api = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(OkHttpClient.build())
.build()
.create(WAApiCall::class.java)
}
return api!!
}
fun call(context: Context) =
getWAApi(context)
}
Function in Main Activity to make the call:
fun testRequest(){
val call = WAApiCallService.call(this)
call.callPost("grant_type=client_credentials&scope=auto")
.enqueue(object: Callback<AuthToken>{
override fun onFailure(call: Call<AuthToken>, t: Throwable) {
Log.i("FAILURE", t.localizedMessage)
}
override fun onResponse(call: Call<AuthToken>, response: Response<AuthToken>) {
Log.i("SUCCESS", "TOKEN = ${response.body().toString()}")
Log.i("SUCCESS", "${response}")
val token = response.body()?.accessToken
Log.i("SUCCESS", "TOKEN = $token")
}
})
}
Error message:
I/SUCCESS: TOKEN = null
I/SUCCESS: Response{protocol=http/1.1, code=400, message=Bad Request, url=https://oauth.wildapricot.org/auth/token}
I think that I am just not understanding how to implement this type of request in some basic way, I could not get it to work in Postman either. I understand that I need to send the credentials to the authentication server, and receive an access token, that will expire and need to be refreshed, and that It will be included in each actual API endpoint call, I guess I'm just missing something crucial in the most important step of that process (getting the actual token, I am imagining it is a simple, forehead slapping kind of misunderstanding on my part?). The wild apricot API is on swagger hub, and I am able to gain access through that UI, with my API key, and see the responses, so I know that it is valid.
Your client credentials request looks mostly all good. The only thing I can see that looks wrong is no space character in the AUTH header between 'Basic' and the encoded credential.
If that doesn't work, could you trace the HTTP request and verify that you are sending the message you think you are.
Thank you for that observation, it led me to figuring out what ultimately was wrong in my initial attempt. After adding that space, I traced the request and found that It was actually sending two headers for content type.
The fix for that was to set the header in the retrofit call from the interface:
interface WAApiCall {
#POST("auth/token")
fun callPost(#Body Body: okhttp3.RequestBody, #Header("Content-type") type: String): Call<AuthToken>
}
As you can see the body is also slightly different, the call was getting through but was returning:
"unsupported_grant_type".
I was passing a raw string as the body parameter, which was including the quotation marks in the request. The solution there was to pass the okhttp3.Request body type rather than a raw string, in the function that makes the actual call it looks like this:
val body: "grant_type=client_credentials&scope=auto&obtain_refresh_token=true"
val requestBody = RequestBody.create("text/plain".toMediaTypeOrNull(),body)
val call = WAApiCallService.call(this)
call.callPost(requestBody,"application/x-www-form-urlencoded")
.enqueue(object: Callback<AuthToken>{
With those changes the call succeeds and my long running headache is over.
I am using Retrofit 2 to upload an audio file to an Azure blob storage service via Azure's REST APIs.
The upload appears to work, but the file stored in the Azure blob container is corrupt because as well as the audio data it contains what appears to be HTTP headers. For example, these are the contents of one uploaded file:
--3c88cdb1-5946-432d-a129-cc8e930d014c
Content-Disposition: form-data; name="tape";
filename="/data/user/0/blahblah.mp4"
Content-Type: audio/mp4
Content-Length: 8365
...expected binary data blah blah blah ....
--3c88cdb1-5946-432d-a129-cc8e930d014c--
What am I doing wrong?
My upload function looks like this:
val tapeFile = File(fileName)
val tapePart = tapeFile.asRequestBody("audio/mp4".toMediaType())
val tapeBodyPart = MultipartBody.Part.createFormData("tape",tapeFile.absolutePath, tapePart)
tapeAzureWebService.uploadTape(url, tapeBodyPart).enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
if (response.isSuccessful) {
etc etc
My Retrofit interface interface is like this:
#Multipart
#PUT
fun uploadTape(#Url url: String,
#Part tape: MultipartBody.Part): Call<ResponseBody>
(It's using #URL because I'm using Azure SAS, with dynamic URLs with authentication embedded in the URL as a series of query strings, and that works very well and is a neat hint for anyone who stumbles on this, by the way, since it prevents Retrofit from encoding the URL and query.)
And my OKHttp Client looks like this, adding some headers that Azure demands:
class TapeAzureWebServiceAPI {
fun service() : TapeAzureWebService {
val headerInterceptor = object: Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val original = chain.request()
val requestBuilder = original.newBuilder()
.header("x-ms-version", "2015-12-11")
.header("x-ms-blob-type","BlockBlob")
val request = requestBuilder.build()
return chain.proceed(request)
}
}
val loggingInterceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
logI("retrofit: $message")
}
}).setLevel(HttpLoggingInterceptor.Level.BODY)
val client : OkHttpClient = OkHttpClient.Builder().apply {
this.addInterceptor(headerInterceptor)
this.addInterceptor(loggingInterceptor)
}.build()
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(AZURE_URL)
.client(client)
.build()
return retrofit.create(TapeAzureWebService::class.java)
}
}
If I use a simple RequestBody rather than a multipart form, I still get the same corruption to the audio file, though there are fewer headers in the audio file.
I've looked at this a long time, and I can't tell if it's something I'm doing wrong in Retrofit, whether Azure wants different headers, or whether Azure simply doesn't like multipart form data.
thanks
John
remove #Multipart just add,
#Headers( "x-ms-blob-type: BlockBlob", "x-ms-blob-content-type: image/png")
#PUT
suspend fun uploadDocument(#Url url: String, #Body request: RequestBody)
and pass request body as,
val mediaType = "image/png".toMediaTypeOrNull()
val body = yourImageFile.asRequestBody(mediaType)
In iOS development, when I fetch an URL that displays XML, I can parse the whole XML file and use its data in my code, but in Kotlin I tried fetching the same URL and it returns only the first XML tag, like if the rest was hidden in the main tag.
val urlString = URL_TO_FETCH_IN_HTTPS (String)
val client = OkHttpClient()
val request = Request.Builder().url(urlString).build()
client.newCall(request).enqueue(object: Callback {
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful) {
val body = response.body?.string()
println("RESPONSE " + body)
}
}
override fun onFailure(call: Call, e: IOException) {
println("Failure")
}
})
The response of this call is just
RESPONSE < ?xml version="1.0" encoding="UTF-8"?>< rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
Although the url is good, and the returned XML in a browser is also good.
So what do I do wrong in the code? How can I fetch the whole XML at this url?
I used the library OkHttp for fetching data from a URL
First of all to debug Okhttp i would suggest to add an interceptor :
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.HEADERS
logging.level = HttpLoggingInterceptor.Level.BODY
Then build you client like this to first add interceptor and have handle timeout :
client = OkHttpClient.Builder()
.addInterceptor(logging)
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build()
Then you can : client.newCall(request).enqueue(object : Callback { ...
By doing so you can easily debug Okhttp you will have on the log the request that you made with the parameter send + then response of the server with the code and the body. All call are writen in blue for better visibilitie.
if you still got probleme don't hesitate to ask.
More over you should look at retrofit2 it's an easy lib to handle all apicall.
I am trying to access IBM's Speech to Text service without using the library. I am using Retrofit with GSON.
The issue is in the authentication, which apparently does not occur correctly, returning code 401. From the official documentation, the HTTP request should come in this format
curl -X POST -u "apikey:{apikey}" \
--header "Content-Type: audio/flac" \
--data-binary #{path_to_file}audio-file.flac \
"{url}/v1/recognize"
When I test the curl command with my credentials, the service works fine.
This is the interface I'm using
interface SpeechToTextApi {
#Multipart
#POST("v1/recognize")
fun speechToText(
#Header("Authorization") authKey: String,
#Part("file") filename: RequestBody,
#Part voiceFile: MultipartBody.Part
): Call<List<SpeechToText>>
}
where I have the following data classes
data class SpeechToText(val results: List<SttResult>)
data class SttResult(val alternatives: List<RecognitionResult>, val final: Boolean)
data class RecognitionResult(val confidence: Float, val transcript: String)
and this is how I set up Retrofit
private val retrofit = Retrofit.Builder()
.baseUrl(STT_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
private val service = retrofit.create(SpeechToTextApi::class.java)
while calling the actual service looks like this
val requestFile = RequestBody.create(MediaType.parse("audio/mp3"), file.name)
val body = MultipartBody.Part.createFormData("file", file.name, requestFile)
service
.speechToText(getString(R.string.stt_iam_api_key), requestFile, body)
.enqueue(object: Callback<List<SpeechToText>> {
override fun onResponse(call: Call<List<SpeechToText>>, response: Response<List<SpeechToText>>) {
val listOfStts = response.body()
Log.d(TAG, "Response code: ${response.code()}")
if (listOfStts != null) {
for (stt in listOfStts) {
for (res in stt.results) {
Log.d(TAG, "Final value: ${res.final}")
for (alt in res.alternatives) {
Log.d(TAG, "Alternative confidence: ${alt.confidence}\nTranscript: ${alt.transcript}")
Toast.makeText(this#MainActivity, alt.transcript, Toast.LENGTH_SHORT).show()
}
}
}
}
}
override fun onFailure(call: Call<List<SpeechToText>>, t: Throwable) {
Log.d(TAG, "Error: ${t.message}")
t.printStackTrace()
}
})
Recordings are MP3 files, for which I am sure they are stored correctly and accessible. I have replaced audio/flac with audio/mp3 as well.
Issue seems to be in the way authentication works. Prior to the code I have shown above, I've used
private val retrofit = Retrofit.Builder()
.baseUrl(STT_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(OkHttpClient.Builder()
.addInterceptor { chain ->
val request = chain.request()
val headers = request
.headers()
.newBuilder()
.add("Authorization", getString(R.string.stt_iam_api_key))
.build()
val finalRequest = request.newBuilder().headers(headers).build()
chain.proceed(finalRequest)
}
.build())
.build()
but the same response code 401 persisted. Of course, the interface method lacked the #Header parameter.
Any sort of help is much appreciated.
I am kind of saddened by the fact nobody was able to solve this one sooner, but here's the solution I came across by accident when working on a different project altogether.
As you can see from the curl command, authentication comes in the form of username: password pattern, in this case, username being apikey string and password is your API key.
So the way you should tackle this is by building your Retrofit instance this way:
fun init(token: String) {
//Set logging interceptor to BODY and redact Authorization header
interceptor.level = HttpLoggingInterceptor.Level.BODY
interceptor.redactHeader("Authorization")
//Build OkHttp client with logging and token interceptors
val okhttp = OkHttpClient().newBuilder()
.addInterceptor(interceptor)
.addInterceptor(TokenInterceptor(token))
.build()
//Set field naming policy for Gson
val gsonBuilder = GsonBuilder()
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
//Build Retrofit instance
retrofit = Retrofit.Builder()
.baseUrl(IBM_BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gsonBuilder.create()))
.client(okhttp)
.build()
}
and create this custom interceptor
class TokenInterceptor constructor(private val token: String) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val original = chain.request()
val requestBuilder = original
.newBuilder()
.addHeader("Authorization", Credentials.basic("apikey", token))
.url(original.url)
return chain.proceed(requestBuilder.build())
}
}
You need to use Credentials.basic() in order to encode credentials.
I really hope somebody with a similar issue stumbles across this and saves themselves some time.
I saw that Content-Type header is removed for methods that don't support a Body, but that isn't my case. I've also confirmed my User-Agent header is successfully set.
This can be done statically via the interface with the endpoint's definition but I'd favor a global Interceptor over annotating all my methods.
// Api.kt
#POST("authenticated_users")
fun postUser(
#Body newUser: NewUser
): Observable<AuthUser>
class UserRepo #Inject constructor(private val api: Api) {
fun postUser(newUser: NewUser) = api.postUser(newUser)
}
// NetModule.kt
#Provides #Singleton
fun providesOkHttpClient(cache: Cache, app: Application): OkHttpClient {
val timeoutInSeconds = 90.toLong()
val builder = OkHttpClient.Builder()
.cache(cache)
.addInterceptor(MyInterceptor(app))
.connectTimeout(timeoutInSeconds, TimeUnit.SECONDS)
.readTimeout(timeoutInSeconds, TimeUnit.SECONDS)
when {
BuildConfig.DEBUG -> {
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.HEADERS
}
with(builder) {
addInterceptor(loggingInterceptor)
addNetworkInterceptor(StethoInterceptor())
}
}
}
return builder.build()
}
#Provides #Singleton
fun providesMoshi(): Moshi {
val jsonApiAdapterFactory = ResourceAdapterFactory.builder()
.add(TermsConditions::class.java)
.add(AuthUser::class.java)
.add(Unknown::class.java)
.build()
val builder = Moshi.Builder()
.add(jsonApiAdapterFactory)
.add(KotlinJsonAdapterFactory())
return builder.build()
}
#Provides #Singleton
fun providesRetrofit(okHttpClient: OkHttpClient, moshi: Moshi): Retrofit {
return Retrofit.Builder()
// .addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(JsonApiConverterFactory.create(moshi))
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(baseUrl)
.client(okHttpClient)
.build()
}
// MyInterceptor.kt
class MyInterceptor #Inject constructor(private val app: Application) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val initialRequest = chain.request()
val finalRequest = setHeaders(initialRequest)
return chain.proceed(finalRequest)
}
private fun setHeaders(initialRequest: Request): Request {
return initialRequest.newBuilder()
// .header("Content-Type", "application/vnd.api+json")
.header("User-Agent", "MyApp v${BuildConfig.VERSION_NAME}")
.build()
}
}
// MyViewModel.kt
fun createUser() {
userObserver = object : DisposableObserver<AuthUser>() {
override fun onNext(authUser: AuthUser) {
statusData.postValue(true)
}
override fun onError(e: Throwable) {
Timber.w(e.localizedMessage)
error.postValue(e.localizedMessage)
}
override fun onComplete() {
// no-op
}
}
userRepo.postUser(newUser)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(userObserver)
}
// log1.txt Retrofit with ScalarsConverterFactory
2018-04-18 15:20:35.772 16491-17436/com.es0329.myapp D/OkHttp: --> POST https://api.es0329.com/v5/authenticated_users
Content-Type: text/plain; charset=UTF-8
Content-Length: 259
User-Agent: MyApp v1.5.1
--> END POST
2018-04-18 15:20:36.278 16491-17436/com.es0329.myapp D/OkHttp: <-- 500 https://api.es0329.com/v5/authenticated_users (505ms)
// log2.txt Retrofit without ScalarsConverterFactory
2018-04-18 18:25:45.742 5017-6325/com.es0329.myapp D/OkHttp: --> POST https://api.es0329.com/v5/authenticated_users
Content-Type: application/json; charset=UTF-8
Content-Length: 311
User-Agent: MyApp v1.5.1
--> END POST
2018-04-18 18:25:45.868 5017-6325/com.es0329.myapp D/OkHttp: <-- 500 https://api.es0329.com/v5/authenticated_users (125ms)
// log3.txt after modifying JsonApiConverterFactory's `MediaType`
2018-04-18 20:35:47.322 19368-19931/com.es0329.myapp D/OkHttp: --> POST https://api.es0329.com/v5/authenticated_users
Content-Type: application/vnd.api+json
Content-Length: 268
User-Agent: MyApp v1.5.1
--> END POST
2018-04-18 20:35:49.058 19368-19931/com.es0329.myapp D/OkHttp: <-- 200 https://api.es0329.com/v5/authenticated_users (1735ms)
Why is it not working
Retrofit is in charge of setting appropriate content type and length based on registered converters and what you provide to your #Body parameter.
In greater detail: A Retrofit converter is responsible for transforming the type of your #Body to okhttp3.RequestBody which holds your content bytes, content length, and content type. Similarly on the way back. You supply content, ResponseBody handles details like HTTP headers.
You can't manually override these headers.
As you can see in the log, your string body gets successfully transmitted as text/plain.
--> POST https://api.es0329.com/v5/authenticated_users
Content-Type: text/plain; charset=UTF-8
Content-Length: 259
User-Agent: MyApp v1.5.1
--> END POST
That leads me to believe you have a registered converter and it's the scalar converter, which states:
A Converter which supports converting strings and both primitives and their boxed types to text/plain bodies.
What to to instead
All of the ready-made converters (Moshi, Gson, Jackson) are built to convert POJOs to application/json. This is a typical case and you should use one of these if you can. Explore source code here.
There are plenty of tutorials online for this case.
Rocky alternative
If for some reason you want/need to continue your current direction, that is prepare a JSON string manually and send that as application/vnd.api+json, you'll need a custom converter.
The aforementioned scalar converter already knows how to transform strings, so copy it into your project and adapt it to your needs (change the mime type). It's just a set of three classes:
convertor factory
request body convertor (transforms the #Body to okhttp3.RequestBody)
repsonse body convertor (transforms the okhttp3.ResponseBody to return value)