File Attachments
Images, documents, audio, and video move through Outeract as File records connected to message events by attachment edges. This guide covers the file lifecycle: receiving files on inbound messages, uploading your own, sending them with sendMessage, and reading the bytes back out.
File operations run on the developer API at https://api.outeract.com/ (Authorization: Bearer <api-key>). Creating files requires the events:write scope.
The File Model
| Field | Type | Description |
|---|---|---|
id | UUID | Unique identifier |
appId | UUID | Application this file belongs to |
url | string | Client-usable URL (GCS references resolve to time-limited signed URLs) |
dataUri | string | Inline data:<mime>;base64,... URI of the file contents (on demand) |
storageType | enum | EMBEDDED (data URI in the database) or GCS (Google Cloud Storage / external URL) |
status | string | Processing status (completed for usable files) |
mimeType | string | MIME type, e.g. image/png |
size | int | Size in bytes |
filename | string | Original filename, if known |
description | string | Optional caption / description |
createdAt | datetime | When the record was created |
The raw storage reference is internal. Clients always read url (resolved at query time) or dataUri (bytes inlined at query time).
Lifecycle
flowchart LR
subgraph Inbound
W["Platform webhook<br/>(media message)"] --> F1["File record"]
F1 -->|attachment edge| E1["message.inbound event"]
end
subgraph Outbound
U["createFile mutation"] --> F2["File record"]
F2 -->|fileIds| S["sendMessage"]
S -->|attachment edge| E2["message.outbound event"]
E2 --> P["Delivered to platform"]
endFiles on Inbound Messages
When a user sends media on a connected platform, Outeract stores the content as a File and links it to the message.inbound event with an attachment edge. Read attachments straight off the event:
query {
events(first: 20, eventTypes: ["message.inbound"]) {
edges {
node {
id
eventType
payload
attachments {
id
mimeType
size
filename
url
}
}
}
}
}Uploading Files
createFile mints a File record you can then attach to outgoing messages. Provide exactly one of data or url:
The mutation returns the File with status: "completed", ready to send. Passing both data and url (or neither) is an error, as is invalid base64.
Sending Files
Pass file IDs to sendMessage. The message text is optional when attachments are present:
mutation {
sendMessage(
recipientUserId: "user-uuid"
message: "Here is your receipt"
fileIds: ["file-uuid"]
) {
id
eventType
attachments {
id
mimeType
}
}
}For each file, Outeract creates an attachment edge from the outbound message event to the File, then delivers the media through the platform integration.
A `sendMessage` call must include at least one of `message`, `template`, or `fileIds`.
Downloading and Accessing Content
By URL
url is the cheap path. Embedded data URIs and external URLs pass through unchanged; GCS-stored files resolve to a time-limited signed HTTPS URL at query time, so fetch it promptly rather than persisting it.
query {
files(fileIds: ["file-uuid"]) {
id
mimeType
size
url
}
}Inline with dataUri
dataUri returns the file contents as a data:<mime>;base64,... string regardless of where the file is stored: embedded files return their stored URI directly, while GCS-stored and externally hosted files are fetched and encoded on demand. This is convenient when the consumer can’t follow URLs (e.g. feeding an image directly to an AI model).
query {
files(fileIds: ["file-uuid"]) {
id
mimeType
dataUri
}
}`dataUri` is only computed when your query selects it, but it inlines the entire file into the GraphQL response. Prefer `url` unless you actually need the bytes in-band, and avoid selecting `dataUri` across large file lists.
If the underlying bytes can’t be fetched (e.g. an unreachable external URL), dataUri returns null.
Attachment Edges in the Graph
Attachments are ordinary graph edges: source is the message event, target is the file, edgeType is attachment. That means you can traverse them like any other relationship:
# All attachment edges for a batch of events
query {
edges(eventIds: ["event-uuid-1", "event-uuid-2"], edgeType: "attachment") {
sourceNodeId
targetNodeId
edgeType
}
}You can also filter the event stream by a related file. For example, every event that references a given file:
query {
events(
relatedNodeId: "file-uuid"
relatedNodeType: "file"
relatedEdgeType: "attachment"
) {
edges {
node {
id
eventType
createdAt
}
}
}
}See Also
- Events Concept - The event ledger files attach to
- Edges Concept - Graph relationships
- GraphQL API - Full schema reference