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