Getting started

Using an AI coding assistant? Copy this prompt
Build a SureDocs integration following https://developer.suredocs.ie/llms.txt

Your assistant reads the rest from this site. Keep your refresh token out of the chat: put it in the environment variable SUREDOCS_REFRESH_TOKEN, where the code it writes should read it from.

This page takes you from nothing to a PDF filed with a brokerage, using curl. Every value in it that you would replace is obviously fake, and nothing here is a real credential.

What you need

The model in one paragraph

An integration is your software. We issue it a refresh token. You exchange the refresh token for an access token, which lasts 1 hour and acts for one brokerage only. The brokerage must have given your integration a grant, which lists what it may do, and the access token carries the scopes both agree on. You send the access token with each call. The brokerage is named in the token and never in a URL, so a call cannot be aimed at another brokerage by changing a path.

integration -> refresh token -> access token -> the brokerage's grant

1. Set your credentials

export CLIENT_ID='int_example_a1b2c3d4'
export REFRESH_TOKEN='<the refresh token we gave you>'
export BROKER_ID='test'

2. Exchange the refresh token for an access token

curl -sS https://api.suredocs.ie/oauth/token \
  --data-urlencode grant_type=refresh_token \
  --data-urlencode client_id="$CLIENT_ID" \
  --data-urlencode refresh_token="$REFRESH_TOKEN" \
  --data-urlencode broker_id="$BROKER_ID" > token.json

The response looks like this:

{
  "access_token": "<the access token, a long opaque string>",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "<the NEXT refresh token>",
  "scope": "upload_file"
}

The refresh token in the response replaces the one you sent, and the one you sent no longer works. Store the new one before you do anything else. In real code you write it to your secret store first and only then use the access token. Authentication explains why, and what to do when a response is lost.

For this walkthrough, keep both in shell variables. Run these two lines only when the response is the one above; after an error they would overwrite REFRESH_TOKEN with null:

export ACCESS_TOKEN=$(jq -r .access_token token.json)
export REFRESH_TOKEN=$(jq -r .refresh_token token.json)

If the request is refused, the body says why. invalid_grant means the refresh token is no longer usable; invalid_target means the brokerage does not exist or has not granted your integration access. Every error is listed in Authentication.

3. Upload a PDF

The file travels base64-encoded inside a JSON body. Encode it into a file first, because a large PDF is too big to pass on a command line:

base64 -w0 example.pdf > example.pdf.b64        # on macOS: base64 -i example.pdf -o example.pdf.b64

jq -n --rawfile content example.pdf.b64 \
  '{filename: "example.pdf", contentType: "application/pdf", contentBase64: $content}' |
curl -sS https://api.suredocs.ie/files/v1 \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  --data-binary @-

The answer says the document was stored:

{
  "status": "stored",
  "fileId": "file_0123456789abcdef0123456789abcdef",
  "requestId": "2f4a5f8e-9d3c-4b6e-8f4a-0c5b1e7d2a90"
}

Send the same request again and the answer is a duplicate. Nothing more is stored, so a retry after a timeout is always safe:

{
  "status": "duplicate",
  "fileId": "file_0123456789abcdef0123456789abcdef",
  "matchedOn": ["filename", "content"],
  "duplicateOf": "file_0123456789abcdef0123456789abcdef",
  "requestId": "6c1d9a52-3f0b-47a8-a1de-5b7f2c4e8d13"
}

A duplicate is decided by content first: the same bytes are a duplicate under any name. We compare with documents stored in the last 18 days and do not remember for longer, so the same bytes sent after that are stored again. The same name with different bytes is a different document. It is stored under a name derived from yours, and the answer says so in storedAs and nameCollisionWith.

A refusal has one shape whatever went wrong. reason is what your code branches on:

{
  "status": 415,
  "reason": "unsupported_file_type",
  "message": "contentType must be one of application/pdf, image/jpeg, text/plain, image/png, audio/wav, audio/mpeg.",
  "requestId": "0b8e3d17-c6a4-4f29-9e5d-7a1c3b5f2e60"
}

What the brokerage sees

The document is classified like any other and reaches the brokerage as a new item in its SureDocs list. It is from Partner upload, and its type is Unknown. It carries the filename you sent, so the brokerage sees that name, and only that brokerage does. Do not put in a filename anything you would not want its staff to read. A brokerage finds the document by typing "Partner upload" in the list's search box, as it would find one from any insurer. Opening it shows what you sent: a PDF opens as a PDF, an image as an image, a text file as text and a recording as a recording, because none is converted into another type on the way in.

Where next