Getting Started

How to integrate the Lens API

So, you've integrated the Lens SDK into your mobile app, producing verified photos and audio/video recordings you can trust – now what? For some use cases, you upload the files to your backend and call it a day. You're simply swapping out unverified captures with verified ones.

Suppose you want to easily extract the verified data sealed within the capture or get the location details corresponding to the latitude and longitude where the capture was taken. That's where the value-added media services provided by the Lens API come into play.

In this guide, we'll walk through an end-to-end integration of these services into your backend app or API. We'll be using Node.js with the Express web application framework, but the code and concepts should be easy enough to translate into the language/framework of your backend.

Set up

Before we get into the details, let's set the stage with a simple, bare-bones Express app that we can flesh out as we move forward. There's nothing special about this – it's just enough to get a web server running on http://localhost:3000 with the necessary dependencies:

PACKAGE.JSON
{
  "type": "module",
  "scripts": {
    "start": "nodemon app.js"
  },
  "dependencies": {
    "express": "^4.17.1",
    "form-data": "^4.0.0",
    "multer": "^1.4.3",
    "node-fetch": "^3.0.0"
  },
  "devDependencies": {
    "nodemon": "^2.0.13"
  }
}
APP.JS
/* Dependencies */
import express from 'express'

/* Constants */
const PORT = 3000

/* Variables */
const app = express()

/* Services */

/* Routes */
app.post('/', (req, res) => {
  res.send({})
})

app.listen(PORT, () => {
  console.log(`Listening on port ${PORT}`)
})

To keep from repeating this code over-and-over as we flesh out the details, we'll simply reference the name of the section (like /* Dependencies */) where the code in question is to live.

If you'd like to run the code yourself along the way, simply install the latest LTS version of Node.js – we like nvm for this – install the dependencies, and start the server:

$ nvm install --lts
$ npm install
$ npm start

Authenticate

🔑

Need a key?

At this time, API keys are managed internally by the Truepic team. If you don't have one yet, please reach out to have one created!

Lens uses API keys to authenticate with its API. Each org unit can have one or more API keys, and each one can be scoped to the exact permissions you desire. This allows you to create a separate key for each platform, environment, or use – whatever makes sense for your setup. Once you have an API key, let's set it as a constant in our code:

/* Constants */
const API_KEY =
  'cTrvjmhDN2qlS43akO/1XnWQ/pL7dF9CyKOKoc1zZ6/Xzw/rE/mPEbjhFstJYiss'
  • Make sure to replace that with your own API key, otherwise you'll receive a 401 Unauthorized response!

API keys should be treated as sensitive secrets and stored securely outside of version control. In other words, we shouldn't be hard-coding the API key in our code like this! Instead, we should store it in an environment variable and reference it via process.env.API_KEY. But, for the sake of this walk-through, we'll roll with hard-coding it for convenience.

Once you have an API key, it should be included in the authorization header of every request as a Bearer token.

Rate Limiting

The Lens API allows up to 120 requests per minute based on IP address. A number of headers are included with every response based on the IETF HTTPAPI Working Group's Draft:

HeaderTypeDescription
ratelimit-limitintegerThe current request rate limit per minute (120).
ratelimit-remainingintegerThe number of requests remaining before rate limiting is triggered.
ratelimit-resetintegerThe number of seconds before the rate limit window is reset.
retry-afterintegerThe same value as ratelimit-reset, but only included once rate limiting has been triggered.

These headers provide a real-time view into the current state of rate limiting, and can be used to programmatically build rate limiting into your integration.

When rate limiting has been triggered, a 429 Too Many Requests error is returned with the following payload:

{
  "error": {
    "message": "Rate limit exceeded, retry in 1 minute",
    "status": 429
  }
}

Upload a Capture for Processing

⚠️

File Size Limit

Please note that there is currently a file size limit of 100 MB per file uploaded to the API, which is approximately 45 seconds of video, depending on the device.

The first significant piece of code we will add is a POST / route that accepts a photo or audio/video upload and sends the file to the Lens API for processing. We included a stub of this route in our initial code:

/* Routes */
app.post('/', (req, res) => {
  res.send({})
})

We're going to leverage three Node.js packages to make this happen. These packages' functionality is so prevalent for a backend web app that you should be able to find similar packages in your language/framework.

  1. Multer to handle the incoming multipart/form-data upload
  2. Form-Data to create the outgoing multipart/form-data request body that includes the uploaded file
  3. Node Fetch to POST said multipart/form-data request to the Lens API

Let's start with the service that uploads the file to the Lens API:

/* Dependencies */
import FormData from 'form-data'
import fetch from 'node-fetch'

/* Services */
async function uploadToLens(file) {
  const form = new FormData()

  form.append(
    'options',
    JSON.stringify({
      verification: true,
    })
  )
  form.append(
    'custom_data',
    JSON.stringify({
      session_id: 123,
    })
  )
  form.append('file', file, 'file.jpg')

  const response = await fetch('https://lens-api.truepic.com/captures', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${API_KEY}`,
    },
    body: form,
  })

  return response.json()
}
  • You'll notice that, besides the file being uploaded, we're also including two JSON-encoded parts in our multipart/form-data body: options and custom_data.
  • options instructs Lens how to process the file. In this case, we're only instructing it to run the verification service, but we could have specified other services to run in parallel, such as location or reverse_image_search.
  • custom_data allows you to associate any custom data you want with the upload. While optional, it's typically helpful to include an identifier that ties the upload to a resource in your system. For example, say your app conducts virtual inspections. You could include an inspection_id to know which inspection the upload is associated with.

With that service in place, let's quickly update our POST / route to accept a capture upload and pass it along to the service:

/* Dependencies */
import multer from 'multer'

/* Variables */
const upload = multer({ storage: multer.memoryStorage() })

/* Routes */
app.post('/', upload.single('file'), async (req, res) => {
  const capture = await uploadToLens(req.file.buffer)

  res.send(capture)
})

The upload is stored in memory (simply for convenience), passed to the service, and then the Lens API response is piped back through so you can easily see what happened. If successful, the response should look something like this:

{
  "capture": {
    "created_at": "2021-09-16T20:55:24.686Z",
    "custom_data": {
      "session_id": 123
    },
    "file_hash": "cLBztEy/HJmLSPnn/BMdz2bisUJOVV4bbztQQiVy/rU=",
    "file_size": 6815727,
    "id": "131b2e2f-e3e2-4503-859d-26ce2df68102",
    "processed_at": null,
    "status": "WAITING",
    "type": "PHOTO",
    "updated_at": "2021-09-16T20:55:24.686Z",
    "uploaded_by_ip_address": "127.0.0.1",
    "url": "https://s3.us-east-2.amazonaws.com/lens-captures-staging/34c4e7c8-e498-43a8-880c-9da96553e982.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIASOSZDTMLGKMVA2E5%2F20220128%2Fus-east-2%2Fs3%2Faws4_request&X-Amz-Date=20220128T170535Z&X-Amz-Expires=36000&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEK%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMiJHMEUCIHW%2Bg7pZQHXnzx7iUsy3sBPR4V0cBVB%2Bnvkm2vBghAeiAiEAvVUsk%2FTCK2%2Fpw1sTNOk81fBYNmkmsLweOEags%2FGafRMqgwQI2P%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARACGgwxNjg3NjQzNDkyMDYiDDdt9OkLcyq5RVHZ1SrXA84eVBlxT8xJ5CadFXM1iD%2FKzexRXrWG9iSJgKRnqBlG303Xs0HFKXKzgX%2B9FGVKpSOIP2Mjh8TbiZ8bpZjOIe%2FK%2FHUW6NYGAeQ9rpCEuTOD7LLS%2BCxaFvD6KQWwzC8peHU7DjmFi40zEtyYRybL%2FC3CzDoEMlFtWXYOJhG%2FF8YdBTbnXFQ%2B23aApSWpAKENLhy11sL8DH8Rp1kQn9Cims55HaU04UhIUJitO4cRRT3lLdX6Zln3bk4cf3MltORhsszhHy18gyZ3psIBC1fPWOa8pBONm4MaKzLuN%2F2yfWehi6YV9WcsnjKzLTLgIvPl25HVbQSB9BJZIEWva9cvBKFRgH9ZI63lJX6wxAvB5chCFNAS9yFlCsvjK3JluyUhSLxPGkatIT3oJ3NrHY%2BTILerPytLn70ICr5iahTArO2emFrCB90tq8qu1cHzzpWdGdF2BZbi993MQ2lN84uOx%2FyJdRbYCbAE5BB76EsApwgMBQHDKVXFN841eW89ZHYgR5c2BuiaupwxZckT5PbgZdxqbsfo9pRn1jLEX%2BfaA1FaWwVvurGAkD2b%2FLquUA03B2H3kDVBSWM36MlgtYkpy3WlPDZYuhjXjbsETcfY2yoCsTKfNfeERDCtiNCPBjqlAU1KsdMNCpCUvjFNwWkYyk%2B4Q2FGYnmf%2BOjwrn6vVnCvC1aLR7vNu1l3wMCfSYyTswsH0X%2FhSj%2BSR7Kgv5GOGYZydAeY7l1xHbpzNboPqu7V64Z64TfPb1C1eNrPEXsBTmeMD%2FSZQdpC43W0ISfp0VXfv%2Bm1%2F464a6SpxI5giQEIdUIlrxZGx3QrwGqOvmKEL5RK5KQPOt3Eok4s7BPHxrQEof4JJg%3D%3D&X-Amz-Signature=7f0028823844fcc760c9fb1e63fd8b2d1f9954ea1734bcc1384d91877025dd50&X-Amz-SignedHeaders=host&x-id=GetObject"
  }
}

The upload is now waiting to be processed! Within a few seconds (typically), it should be finished, as instructed by the options we passed in, and ready for use.

But how do we know when that happens? The answer is webhooks.