Help with Sending a POST Request with Image Attachment to KoboToolbox

Hello everyone,

I’m having a lot of trouble figuring out how to send a POST request with an image attachment.

I’m developing a React Native application where users can fill out forms directly from the app. While making the POST request, I can successfully save all fields except the image fields. From what I understand, the JSON body I need to send looks like this:

{
“id”: “a39XfjhfhUfLVJZtUDTbMe”,
“submission”: {
“start”: “2025-03-05T16:15:13.575Z”,
“end”: “2025-03-05T16:15:13.575Z”,
“…data”: “…”,
version”: “v6fy5sQnMQBZ6Tbnxh5JoH”,
version”: “vE7YnKqgB3LVEjm8qqr6sH”,
“_version__001”: “v4qFGRdWiaUmeuWzx2kA4L”,
“formhub”: {
“uuid”: formId
},
“meta”: {
“instanceID”: “uuid:randomId”
}
}
}

My problem is understanding how, where, and in what format to include the image attachments. If I add the image field (e.g., "sign_") inside submission and assign it the Base64-encoded image as a value, the POST request is sent successfully, but the image does not load in KoboToolbox. When I check DevTools, I see that my uploaded image is linked to:
https://kf.kobotoolbox.org/undefined (which returns a 404 error),
whereas a working image is linked to:
https://kf.kobotoolbox.org/api/v2/assets/<_uuid>/data/<_id>/attachments/<id>/ (which returns 200).

I have also tried sending the POST request using FormData, but I keep getting a JSON Parse Error, likely because I’m sending the request to kc.kobotoolbox.org/api/v1/submissions, which I assume only accepts JSON.

Additionally, the kobo-sdk library does not work in React Native due to missing dependencies that exist in Node.js but not in React Native, so I cannot rely on that either. Sending the POST request to kc.kobotoolbox.org/api/v1/submissions.json does not solve the issue either.

After multiple failed attempts and extensive research, I have not found any documentation on how to send a POST request with an image attachment in JSON format. Could anyone guide me on how to properly send an image attachment with a POST request in KoboToolbox?

Thank you in advance for your help!

2 Likes

In KoboToolbox, images should be uploaded as file attachments rather than being embedded in JSON as Base64. Since you’re using React Native, you need to send a multipart/form-data request instead of JSON. Here’s how you can structure your request:

  1. Use FormData to send the image as a file:
const formData = new FormData();
formData.append("id", "a39XfjhfhUfLVJZtUDTbMe");
formData.append("submission", JSON.stringify({
  start: "2025-03-05T16:15:13.575Z",
  end: "2025-03-05T16:15:13.575Z",
  version: "v6fy5sQnMQBZ6Tbnxh5JoH",
  meta: { instanceID: "uuid:randomId" }
}));
formData.append("sign_", {
  uri: imageUri, // Local file path
  name: "signature.jpg",
  type: "image/jpeg"
});

fetch("https://kc.kobotoolbox.org/api/v1/submissions", {
  method: "POST",
  headers: { "Content-Type": "multipart/form-data" },
  body: formData
})
.then(response => response.json())
.catch(error => console.error(error));
  1. Ensure the imageUri points to a valid local file path.

  2. The endpoint kc.kobotoolbox.org/api/v1/submissions should accept multipart form-data, so avoid sending JSON headers.

This approach should correctly attach the image to the KoboToolbox submission.

1 Like

Thanks for the reply! However I already tried to submit a formdata but I always get “JSON Parse Error: unexpected character <”

The function I’m using is this:

async function submitSurvey() {
try {
//GET UUID FORMHUB
const credentials = encodeBase64(${user.username}:${user.password});
const formListResponse = await fetch(‘https://kc.kobotoolbox.org/api/v1/forms’, {
method: ‘GET’,
headers: {
‘Authorization’: Basic ${credentials},
‘Content-Type’: ‘application/json’,
},
});
const formList = await formListResponse.json();
const formUuid = formList.find(el => el.id_string === form.uid)?.uuid;
if (!formUuid) throw new Error(‘Form UUID not found’);
/// CREATE NESTED OBJECT FOR THE SUBMISSION
const surveyFields = form.content.survey;
let nestedObject = {};
let groupStack = [empty array]
let currentContext = nestedObject;

  surveyFields.forEach(field => {
    if (field.type === 'begin_group') {
      let newGroup = {};
      currentContext[field.name] = newGroup;
      groupStack.push(currentContext);
      currentContext = newGroup;
    } else if (field.type === 'end_group') {
      currentContext = groupStack.pop() || nestedObject;
    } else if (formData.hasOwnProperty(field.name)) {
      if(!['image', 'audio', 'video'].includes(field.type)) {
        currentContext[field.name] = formData[field.name];
      }
    }
  })
  //CREATE THE BODY FOR THE POST
  const objectSubmission = {
    ...nestedObject,
    formhub: { uuid: formUuid },
    meta: { instanceID: `uuid:${uuid.v4()}` },
  };
  const formDataNew = new FormData()
  formDataNew.append("id", form.uid)
  formDataNew.append("submission", JSON.stringify({objectSubmission}))
  surveyFields.forEach(field => {
    if (['image', 'video', 'audio'].includes(field.type) && formData[field.name] !== undefined) {
      formDataNew.append(field.name, {
        uri: formData[field.name],
        name: `signature_${new Date()}`,
        type: 'image/png'
      })
    }
  })

  //FETCH POST FOR SUBMIT FORM
  await fetch('https://kc.kobotoolbox.org/api/v1/submissions', {
    method: 'POST',
    headers: {
      'Authorization': `Basic ${credentials}`,
      'Content-Type': 'multipart/form-data',
    },
    body: formDataNew,
  });
        Toast.show(t('survey_submit'), {
    duration: Toast.durations.SHORT,
    position: Toast.positions.BOTTOM,
    shadow: true,
    animation: true,
    hideOnPress: true,
  });
  navigation.navigate('Survey Page');
} catch (error) {
  console.error(error.message)
  Toast.show(error.message, {
    duration: Toast.durations.SHORT,
    position: Toast.positions.BOTTOM,
    shadow: true,
    animation: true,
    hideOnPress: true,
  });
} finally {
  setLoading(false);
}

}

Tell me if I’m the one who is doing something wrong because I’m going crazy to understand how to get it work.

Basically formdata has every name’s Field ad key with the vale to send so after organize it for the submission I go with the attachments

P.S. formData[field.name] has this path here: file:///storage/emulated/0/DCMI/sign_20250310.png, which is the path where I save the signature with react-native-signature-canvas

1 Like

I’m trying to send a POST request to kc.kobotoolbox.org with images ad attachments on a React Native Expo app using multipart/form-data but I keep getting “JSON Paese Error: Unexpected character <”

Even in Postman when I try to send a form-data I get Error 500.

Since everywhere I see tells ne that to send attachment I need to send a Formdata please help me understand how I could do so on React Native