Skip to content
campaiDeveloper
campai.com

Uploading bytes and attaching a file to a record are two separate requests. First obtain a temporary upload URL, then send the bytes to storage, then pass the returned resource ID to the endpoint that owns the attachment.

This guide attaches a small file to a designated test contact. It creates a contact document, so use a synthetic contact and a key with Mandates → Read, access to that contact's type, and Contact documents → Insert and Read. A read-only key can request an upload URL but cannot create the document.

What you’ll have at the end

A contact document whose downloaded content matches the bytes you uploaded.

  1. Get signed URL
  2. PUT bytes
  3. Attach resource ID
  4. Open document
Before you start
  • A synthetic contact
  • Mandates Read, contact access, and Contact documents Insert/Read
  • A small test file or the included text payload
On this page
Step Request Result
Request temporary storage Get upload URL { id, url }
Send file bytes HTTP PUT to the returned signed url Storage accepts the bytes
Attach the resource Create document A document _id

The signed URL authorizes the storage upload. Do not add your campai X-API-Key header to that request. Send raw bytes, not JSON, Base64, or a multipart wrapper. Preserve the signed URL's query string.

The reviewed upload URL expires after 15 minutes. Obtain it shortly before uploading. The id belongs in file.resource; the URL does not. A storage upload by itself creates no contact document.

Replace YOUR_API_KEY with your key. This request returns a temporary url and a resource id; keep both for the next steps.

Example

curl --fail-with-body --silent --show-error --max-time 30 --globoff \
  --request GET 'https://cloud.campai.com/api/misc/storage/uploadUrl' \
  --header 'X-API-Key: YOUR_API_KEY'

Replace the complete https://REPLACE_WITH_SIGNED_UPLOAD_URL value with the returned url, including its query string. These requests upload the same short text directly, so no local file or library is required. Continue only after storage returns a successful HTTP status.

Example

curl --fail-with-body --silent --show-error --max-time 30 --globoff \
  --request PUT 'https://REPLACE_WITH_SIGNED_UPLOAD_URL' \
  --header 'Content-Type: application/octet-stream' \
  --data-binary 'Example attachment'

The storage request has no X-API-Key header. For a real file, supply its bytes using cURL’s --upload-file, Node.js’s built-in readFile, or PHP’s file_get_contents. Keep the uploaded bytes and the filename in the attachment request consistent.

Replace the key and IDs, including YOUR_CONTACT_ID and YOUR_UPLOAD_RESOURCE_ID. The resource ID is the id returned in step one, not the signed URL. This request creates a contact document:

Example

curl --fail-with-body --silent --show-error --max-time 30 --globoff \
  --request POST 'https://cloud.campai.com/api/YOUR_ORGANIZATION_ID/YOUR_MANDATE_ID/crm/contacts/YOUR_CONTACT_ID/documents' \
  --header 'X-API-Key: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "name": "Integration test file",
  "description": "",
  "shareToContact": false,
  "file": {
    "resource": "YOUR_UPLOAD_RESOURCE_ID",
    "fileName": "attachment.txt"
  }
}'

Save the returned document _id. Do not repeat Create document automatically if the response is lost; check the contact’s documents first.

Get document returns the document directly. Its file resource is a signed read URL, with file metadata. Confirm the name, filename, sharing setting, and content using the contact's document view or an authorized download. Keep signed URLs out of permanent identifiers and public logs; they grant temporary access to file content.

shareToContact: false keeps this example from enabling contact sharing. If your feature needs sharing, make that a deliberate product decision and test the resulting contact access separately.

To remove the test attachment, use Delete document with its returned ID and Contact documents → Remove. Scope cleanup to the fixture you created. Do not delete other documents merely because their names look similar.

A 403 from the API concerns key scope or mandate/contact access; a 403 from storage can concern an expired or altered signed URL. These are different services. Check which request failed before changing permissions.

If PUT fails, do not call Create document. If attachment creation times out, inspect the contact's documents before repeating it. The server may already have attached the resource. Preserve your local operation record and returned ID whenever available.

Other endpoints may accept a storage resource object or their own receiptFileId fields. Follow the destination endpoint's schema. Do not post a contact-document payload to an organization avatar or receipt operation just because all three involve files.

Check your result

Upload fails
No attachment request is sent.
Attachment succeeds
Name, content, and sharing match the intended file.
Create response is lost
Inspect documents before retrying.
Continue with Handling files