Multipart/form-data payloads

Our Upload endpoints require a payload of type multipart/form-data as defined by RFC 7578. Most HTTP clients provide features that simplify this process.

Best practices for filenames in Upload APIs

Allowed characters

Filenames should use only safe and compatible characters as follow:

  • Alphanumeric characters: A-Z, a-z, 0-9
  • Hyphens (-)
  • Underscores (_)
  • Periods (.) for file extensions
  • Spaces may be allowed but should be URL-encoded (e.g., %20 or +)

Avoid special characters

Some characters can cause issues in file systems and URLs and should be avoided. These include:

  • Slashes (/, \): reserved for path separators
  • Backslashes (\): commonly reserved, especially on Windows systems
  • Wildcards (*, ?)
  • Angle brackets (<, >)
  • Colon (:), pipe (|)
  • Quotes ("), apostrophes (')
  • Ampersand (&), percent (%)

📘

Note:

Using these characters may require URL encoding or preferably, excluding them from filenames entirely.

Length limitations

For compatibility and ease of use, keep filenames under 255 characters.

You can use any library or platform that can send HTTP requests. This page offers code samples for some popular technologies, including:

cURL

You can upload a file to Ocrolus using cURL with its--form option. Use the @ syntax to refer to a particular file on your file system.

curl \
  --url "https://api.ocrolus.com/v1/book/upload"
  --oauth2-bearer "eyJhb...ciOSiU" \
  --form book_uuid="$THE_BOOK_UUID" \
  --form upload="@./path/to/document.pdf"

📘

Which operating system?

Most shells will handle the preceding syntax identically. The most notable exceptions are PowerShell and Windows CMD.

They respectively use backticks (`) and carets (^) instead of backslashes (\) for line continuations.

Python

You can use the requests library and pass the files to requests.post's files keyword parameter, as demonstrated in the following code sample.

You can also use the oauthlib library to create a session. The library will automatically apply the correct Content-Type and Authorization headers when used as shown. Remember to close the opened file (possibly using a with statement) once you're done with it!

import requests

response = requests.post(
                  'https://api.ocrolus.com/v1/book/upload',
                  headers={
                      'Accept': 'application/json',
                      'Authorization': 'Bearer <BEARER_TOKEN>',
                  },
                  data={
                      'pk': '123456',
                  }
                  files={
                      'upload': open('/path/to/bank-statement.pdf', 'rb'),
                  },
              )

print(response.json())
# You'll need to install the requests and requests-oauthlib libraries
from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session

client_id = "your_client_id"
client_secret = "your_client_secret"
# Get these from the Ocrolus Dashboard (and don't share them with anyone)

client = BackendApplicationClient(client_id=client_id)
session = OAuth2Session(client=client)
token = session.fetch_token("https://auth.ocrolus.com/oauth/token", client_secret=client_secret)

filepath = "/path/to/the/file.pdf"
url = "https://api.ocrolus.com/v1/book/upload"

data = {"pk": "the_book_pk"}  # The book's primary key, as a string or an int

with open(filepath, "rb") as file:
    files = {"upload": file}
    response = session.post(url, data=data, files=files)
    print(response.json())

Node.js

Axios is a popular option that pairs well with the form-data package, as demonstrated below:

const fs = require('fs'),
           axios = require('axios'),
        FormData = require('form-data');

const form_data = new FormData();
form_data.append('pk', '123456');
form_data.append('upload', fs.createReadStream('./index.js'));

const ocrolusApi = axios.create({
  baseURL: 'https://api.ocrolus.com/v1/'
});

ocrolusApi.post(
  'https://api.ocrolus.com/v1//book/upload', 
  form_data, 
  { headers: form_data.getHeaders(),
    authorization: 'Bearer <BEARER_TOKEN>'
}
).then(res => {
  console.log('response', res.data);
}).catch(err => {
  console.log('error', err);
});

👍

Prefer something else?

The preceding code sample is written in JavaScript, but you can use the demonstrated APIs with any language that
compiles to it (e.g. CoffeeScript or TypeScript).

Java

You can encode multipart/form-data payloads with APIs offered by Spring or Java EE. You could use Spring's MultipartBodyBuilder or Java EE's MimeMultipart classes.

import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.core.io.FileSystemResource;
import org.springframework.http.MediaType;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.web.reactive.function.client.WebClient;

public class Main {
  public static void main(String[] args) {
    WebClient client = WebClient.builder().
        baseUrl("https://api.ocrolus.com/v1/").
        defaultHeaders(h -> {
          h.setBasicAuth("api-key", "api-secret");
          h.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
        }).
        build();

    MultipartBodyBuilder bookUploadBuilder = new MultipartBodyBuilder();
    bookUploadBuilder.part("pk", 123456);
    bookUploadBuilder.part("upload", new FileSystemResource("path/to/bank-statement.pdf"));

    System.out.println(client.post().uri("book/upload").bodyValue(bookUploadBuilder.build()).
        retrieve().bodyToMono(String.class).block());
  }
}
import java.net.URI;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.core.io.FileSystemResource;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.web.client.RestTemplate;

public class Main {
  public static void main(String[] args) {
    RestTemplate client = new RestTemplate();

    MultipartBodyBuilder bookUploadBuilder = new MultipartBodyBuilder();
    bookUploadBuilder.part("pk", 123456);
    bookUploadBuilder.part("upload", new FileSystemResource("./Main.java"));

    RequestEntity bookUploadRequest = RequestEntity.
        post(URI.create("https://api.ocrolus.com/v1/book/upload")).
        accept(MediaType.APPLICATION_JSON).
        headers(h -> h.setBasicAuth("api-key", "api-secret")).
        body(bookUploadBuilder.build());

    System.out.println(client.exchange(bookUploadRequest, String.class).getBody());
  }
}
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

import java.net.URL;
import java.net.HttpURLConnection;

import java.util.Base64;

import java.util.stream.Collectors;

import javax.mail.MessagingException;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMultipart;

class Main {
  public static void main(String[] args) throws IOException, MessagingException {
    MimeMultipart multipart = makePayload("123456", "path/to/bank-statement.pdf");
    
    URL url = new URL("https://api.ocrolus.com/v1/book/upload");
    HttpURLConnection connection = (HttpURLConnection)url.openConnection();
    connection.setDoOutput(true);

    connection.setRequestProperty("Authorization", makeAuthorizationHeader("api-key", "api-secret"));
    connection.setRequestProperty("Content-Type", multipart.getContentType().replaceAll("(?ms)\\s*\r\n\\s*", " "));
    connection.setRequestMethod("POST");
    multipart.writeTo(connection.getOutputStream());

    connection.connect();

    System.out.println(String.format("[%d] %s", connection.getResponseCode(), connection.getResponseMessage()));

    BufferedReader reader = new BufferedReader(new InputStreamReader((InputStream)connection.getContent()));
    System.out.println(reader.lines().collect(Collectors.joining("\n")));
  }

  private static String makeAuthorizationHeader(String username, String password) throws UnsupportedEncodingException {
    byte[] byteData = String.format("%s:%s", username, password).getBytes();
    return String.format("Basic %s", Base64.getMimeEncoder().encodeToString(byteData));
  }

  private static MimeMultipart makePayload(String bookPK, String filePath) throws IOException, MessagingException {
    MimeBodyPart bookPKPart = new MimeBodyPart();
    bookPKPart.setText(bookPK);
    bookPKPart.setDisposition("form-data; name=pk");
    bookPKPart.addHeader("Content-Type", "text/plain; charset=utf-8");

    MimeBodyPart filePart = new MimeBodyPart();
    filePart.attachFile(new File(filePath));
    filePart.setDisposition("form-data; name=upload");

    MimeMultipart multipart = new MimeMultipart();
    multipart.setSubType("form-data");
    multipart.addBodyPart(bookPKPart);
    multipart.addBodyPart(filePart);

    return multipart;
  }
}

👍

Prefer something else?

The preceding code samples are written in Java, but the demonstrated APIs are usable with any language that runs on
the JVM (e.g. Kotlin or Scala).

.NET

You can use .NET's HttpClient and MultipartFormDataContent classes to encode your request.

using System;
using System.IO;
using System.Text;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

class MainClass {
  public static string BOOK_PK = "123456";

  public static void Main (string[] args) {
    HttpClient client = new HttpClient() { BaseAddress = new Uri("https://api.ocrolus.com/v1/") };
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
      "Basic", MakeAuthorizationHeaderValue("api_key", "api_secret"));

    WriteResponse(client.PostAsync("book/upload", MakePayload(BOOK_PK, "./path/to/bank-statement.pdf")));
  }

  private static string MakeAuthorizationHeaderValue(string username, string password) {
    return Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes($"{username}:{password}"));
  }
  
  private static HttpContent MakePayload(string bookPk, string filePath) {
    MultipartFormDataContent content = new MultipartFormDataContent();

    // Add Book PK to payload
    content.Add(new StringContent(bookPk), "pk");

    // Add File content to payload
    FileInfo file = new FileInfo(filePath);
    content.Add(new StreamContent(file.OpenRead()), "upload", file.Name);
    
    // Debug payload
    // content.CopyToAsync(Console.OpenStandardOutput()).Wait();

    return content;
  }

  private static void WriteResponse(Task<HttpResponseMessage> responseTask) {
    Console.WriteLine(responseTask.Result.Content.ReadAsStringAsync().Result);
  }
}

👍

Prefer something else?

The preceding code sample is written in C#, but the demonstrated APIs are usable with any language that uses the .NET runtime (e.g. F# or Visual Basic).

Go

You can use Go's standard http and multipart packages to encode your request.

package main

import (
    "io"
    "log"
    "mime/multipart"
    "net/http"
    "os"
    "strconv"
)

func main() {
    req, err := newUploadRequest(123456, "path/to/bank-statement.pdf")
    if err != nil {
        log.Fatal(err)
    }
    req.SetBasicAuth("api-key", "api-secret")

    client := &http.Client{}

    res, err := client.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    defer res.Body.Close()

    _, err = io.Copy(os.Stdout, res.Body)
    if err != nil {
        log.Fatal(err)
    }
}

func newUploadRequest(bookPK int64, filePath string) (*http.Request, error) {
    body, contentType, err := newUploadBody(bookPK, filePath)
    if err != nil {
        return nil, err
    }

    req, err := http.NewRequest(http.MethodPost, "https://api.ocrolus.com/v1/book/upload", body)
    if err != nil {
        return nil, err
    }
    req.Header.Set("Content-Type", contentType)

    return req, nil
}

func newUploadBody(bookPK int64, filePath string) (io.Reader, string, error) {
    bodyReader, bodyWriter := io.Pipe()
    uploadWriter := multipart.NewWriter(bodyWriter)

    file, err := os.Open(filePath)
    if err != nil {
        return nil, "", err
    }

    go func() {
        defer bodyWriter.Close()
        defer file.Close()

        err = uploadWriter.WriteField("pk", strconv.FormatInt(bookPK, 10))
        if err != nil {
            bodyWriter.CloseWithError(err)
            return
        }

        part, err := uploadWriter.CreateFormFile("upload", file.Name())
        if err != nil {
            bodyWriter.CloseWithError(err)
            return
        }

        _, err = io.Copy(part, file)
        if err != nil {
            bodyWriter.CloseWithError(err)
            return
        }

        err = uploadWriter.Close()
        if err != nil {
            bodyWriter.CloseWithError(err)
            return
        }
    }()

    return bodyReader, uploadWriter.FormDataContentType(), nil
}

PHP

You can use PHP's cURL extension with the CURLFile class to encode your request.

<?php

$username = "username";
$password = "password";
$pk = '123456';
$filepath = "path/to/pdf";
$filename = "bank-statement.pdf";

$request = curl_init('https://api.ocrolus.com/v1/book/upload');
$postdata = array(
  'pk' => $pk,
  'upload' => curl_file_create(
    $filepath . '/' . $filename,
    'application/pdf',
    $filename
  )
);

curl_setopt($request, CURLOPT_USERPWD, $username . ":" . $password);
curl_setopt($request, CURLOPT_POST, true);
curl_setopt($request, CURLOPT_POSTFIELDS, $postdata);

curl_setopt($request, CURLOPT_RETURNTRANSFER, true);
echo curl_exec($request);

curl_close($request);

?>