GraphQL API#
The primary API for interacting with Outeract. Every operation is a GraphQL query or mutation against a single endpoint.
Endpoints#
| Endpoint | Auth | Description |
|---|---|---|
POST / | API Key | Developer API for programmatic access |
Operations that manage the account itself (organisations, applications, platform connections, API keys, admin users, webhook subscriptions and event schemas) are performed in the console rather than through this API.
Headers#
Authorization: Bearer YOUR_API_KEY
Content-Type: application/jsonNote: API keys are already scoped to a specific application, so no
X-Outeract-App-IDheader is needed.
Schema Exploration#
Access the interactive GraphQL playground at the endpoint URL. Introspection is enabled by default:
{
__schema {
types {
name
description
}
}
}Developer API#
Operations available with API key authentication.
Events#
events#
Query events with Relay-style cursor pagination, filtering, and relation-based queries.
query Events($first: Int, $after: String, $eventTypes: [String!]) {
events(first: $first, after: $after, eventTypes: $eventTypes) {
edges {
node {
id
eventTypeName
payload
createdAt
processedAt
edges {
edgeType
targetNodeType
targetNodeId
}
}
cursor
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
count
}
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
first | Int | - | Number of events (forward pagination) |
last | Int | - | Number of events (backward pagination) |
after | String | null | Cursor for forward pagination |
before | String | null | Cursor for backward pagination |
eventTypes | [String!] | null | Filter by event type names |
eventIds | [UUID!] | null | Fetch specific events by ID |
userId | UUID | null | Filter events linked to a user |
conversationId | UUID | null | Filter events in a conversation |
originEventId | UUID | null | Filter by parent event |
relatedTo | RelationFilter | null | Filter by relationships to other events |
relatedNodeId | UUID | null | Only events having an edge (event as source) targeting this node, of any node type (e.g. a user). ANDs with all other filters; applied at the database level so cursor pagination stays exact |
relatedNodeType | String | null | With relatedNodeId, require the edge’s target node type (e.g. "user"). Ignored without relatedNodeId |
relatedEdgeType | String | null | With relatedNodeId, require the edge’s edge type (e.g. "sent_to", "participant"). Ignored without relatedNodeId |
payloadFilters | [PayloadFilter!] | null | Filter by payload field values |
since | String | null | ISO datetime; only events after this time |
minimal | Boolean | false | Return minimal event data |
includeTiming | Boolean | false | Include query timing breakdown |
event#
Get a single event by ID.
query Event($id: UUID!) {
event(id: $id) {
id
eventTypeName
payload
createdAt
originEventId
edges {
edgeType
targetNodeType
targetNodeId
}
}
}Each edge exposes only edgeType, sourceNodeType/sourceNodeId,
targetNodeType/targetNodeId, and extraData. Resolve the target entity
with a follow-up query (identities(identityIds:), files(fileIds:),
users(userIds:)). See Edges.
createEvent#
Create a custom event with optional edges. Requires events:write scope.
mutation CreateEvent(
$userId: UUID!
$payload: JSON!
$eventType: String
$edges: [JSON!]
) {
createEvent(
userId: $userId
payload: $payload
eventType: $eventType
edges: $edges
) {
id
eventTypeName
payload
createdAt
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
userId | UUID! | required | User ID to associate the event with |
payload | JSON! | required | Event payload data |
eventType | String | null | Event type name (e.g., "order.created") |
eventTypeId | UUID | null | Event schema ID (alternative to eventType) |
originEventId | UUID | null | Parent event ID for event chains |
edges | [JSON!] | null | Edges to create with the event |
idempotencyKey | String | null | Client-supplied key for safe retries; see Idempotency |
updateEvent#
Update an event’s payload. Requires events:write scope.
mutation UpdateEvent($eventId: UUID!, $payload: JSON, $mergePayload: Boolean) {
updateEvent(eventId: $eventId, payload: $payload, mergePayload: $mergePayload) {
id
payload
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
eventId | UUID! | required | Event ID to update |
payload | JSON | null | New payload data |
mergePayload | Boolean | true | If true, merge with existing payload; if false, replace |
deleteUserEvents#
Delete events associated with a user. Requires events:write scope.
mutation DeleteUserEvents($userId: UUID!, $before: DateTime, $since: DateTime) {
deleteUserEvents(userId: $userId, before: $before, since: $since)
}Returns Int, the number of events deleted.
| Parameter | Type | Default | Description |
|---|---|---|---|
userId | UUID! | required | User whose events to delete |
before | DateTime | null | Only delete events before this time |
since | DateTime | null | Only delete events after this time |
eventCursorAtDatetime#
Get a cursor positioned at a specific datetime, for use with the events query.
query { eventCursorAtDatetime(datetime: "2024-06-01T00:00:00Z") }eventCursor#
Get the cursor for a specific event by ID.
query { eventCursor(eventId: "event-uuid") }eventTypeStats#
Get event counts grouped by event type over a time period.
query { eventTypeStats(days: 30) { entityId date count } }eventTypeLastActivity#
Get the timestamp of the most recent event for each event type.
query { eventTypeLastActivity { eventType lastEventAt } }resolveId#
Resolve a prefixed ID to a full UUID.
query { resolveId(prefix: "msg", userId: "user-uuid", recordType: "event") }Users#
users#
Query users with Relay-style cursor pagination and search.
query Users($first: Int, $search: String, $orderBy: String) {
users(first: $first, search: $search, orderBy: $orderBy) {
edges {
node {
id
name
isSystemUser
lastActiveAt
createdAt
identities {
id
externalId
identityType
platformConnection { id platformName }
}
}
cursor
}
pageInfo {
hasNextPage
endCursor
count
}
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
first / last | Int | - | Number of users to return |
after / before | String | null | Cursor for pagination |
search | String | null | Search by name, user ID, external ID, or identity ID |
orderBy | String | "created_at" | "created_at" or "last_active_at" |
userIds | [UUID!] | null | Fetch specific users by ID (bypasses pagination) |
configFilters | [ConfigFilter!] | null | Filter by config_data fields |
ConfigFilter matches json_extract_text(config_data, field) == value:
# Only system users
users(configFilters: [{ field: "system_user", value: "true" }]) { ... }identities#
Query user identities (platform accounts).
query {
identities(identityType: "whatsapp", limit: 50) {
id
externalId
identityType
user { id name }
platformConnection { id platformName }
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
identityIds | [UUID!] | null | Fetch specific identities by ID |
externalIds | [String!] | null | Fetch by external IDs |
userId | UUID | null | Filter by user |
identityType | String | null | Filter by type (e.g., "whatsapp", "slack") |
limit | Int | 100 | Maximum to return |
offset | Int | 0 | Offset for pagination |
createUser#
mutation CreateUser($name: String, $isSystemUser: Boolean) {
createUser(name: $name, isSystemUser: $isSystemUser) {
id
name
isSystemUser
createdAt
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
name | String | null | Display name |
isSystemUser | Boolean | false | Whether this is a system/bot user |
createIdentity#
Create a platform identity for a user.
mutation CreateIdentity(
$userId: UUID!
$externalId: String!
$identityType: String!
$config: JSON
) {
createIdentity(
userId: $userId
externalId: $externalId
identityType: $identityType
config: $config
) {
id
externalId
identityType
user { id name }
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
userId | UUID! | required | User to attach the identity to |
externalId | String! | required | External identifier (phone number, username, etc.) |
identityType | String! | required | Platform or custom type (e.g., "whatsapp", "slack", "phone") |
config | JSON | null | Additional identity configuration |
transferIdentities#
Transfer all identities from one user to another (user merge).
mutation TransferIdentities(
$sourceUserId: UUID!
$targetUserId: UUID!
$deleteSourceUser: Boolean
$deleteSourceConversations: Boolean
) {
transferIdentities(
sourceUserId: $sourceUserId
targetUserId: $targetUserId
deleteSourceUser: $deleteSourceUser
deleteSourceConversations: $deleteSourceConversations
) {
id
name
identities { id externalId }
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
sourceUserId | UUID! | required | User to transfer identities from |
targetUserId | UUID! | required | User to transfer identities to |
deleteSourceUser | Boolean | true | Delete the source user after transfer |
deleteSourceConversations | Boolean | true | Delete orphaned conversations |
transferIdentity#
Transfer a single identity to a different user.
mutation { transferIdentity(identityId: "id-uuid", targetUserId: "user-uuid") { id externalId } }deleteIdentity#
mutation { deleteIdentity(identityId: "id-uuid") }Returns Boolean.
deleteUser#
mutation { deleteUser(id: "user-uuid") }Returns Boolean.
Conversations#
conversations#
List conversations with Relay-style pagination and search.
query {
conversations(first: 25, search: "Alice") {
edges {
node {
id
title
createdAt
updatedAt
messageCount
participants { userId userName }
}
cursor
}
pageInfo { hasNextPage endCursor count }
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
first | Int | 25 | Number of conversations to return |
after | String | null | Cursor for pagination |
search | String | null | Filter by title or participant name |
userConversations#
Get conversations for a specific user.
query {
userConversations(userId: "user-uuid", limit: 50) {
id
title
messageCount
participants { userId userName }
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
userId | UUID! | required | User ID |
limit | Int | 50 | Maximum conversations to return |
deleteConversations#
mutation {
deleteConversations(conversationIds: ["conv-uuid-1"], deleteMessages: true)
}Returns Int, the number of conversations deleted.
| Parameter | Type | Default | Description |
|---|---|---|---|
conversationIds | [UUID!]! | required | Conversation IDs to delete |
deleteMessages | Boolean | false | Also delete all messages in the conversations |
Messages#
sendMessage#
Send a message to a user. Requires at least one of message, template, or fileIds.
mutation SendMessage(
$message: String
$recipientUserId: UUID
$conversationId: UUID
$platform: PlatformName
$platformConnectionId: UUID
$fileIds: [UUID!]
$template: JSON
) {
sendMessage(
message: $message
recipientUserId: $recipientUserId
conversationId: $conversationId
platform: $platform
platformConnectionId: $platformConnectionId
fileIds: $fileIds
template: $template
) {
id
eventTypeName
payload
createdAt
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
message | String | null | Text content |
recipientUserId | UUID | null | Recipient user ID |
conversationId | UUID | null | Send to an existing conversation |
senderUserId | UUID | null | Override sender (defaults to system user) |
platform | PlatformName | null | Target platform |
platformConnectionId | UUID | null | Specific platform connection to use |
fileIds | [UUID!] | null | File attachments |
template | JSON | null | Template message (see below) |
idempotencyKey | String | null | Client-supplied key for safe retries; see Idempotency |
Template structure:
| Field | Type | Description |
|---|---|---|
name | String (required) | Template name as registered with the platform |
language | String (required) | Language code (e.g. "en", "en_US") |
components | Array | Template components with parameter substitutions |
Files#
files#
Fetch files by their IDs (batch query).
query {
files(fileIds: ["file-uuid-1", "file-uuid-2"]) {
id
filename
mimeType
size
url
createdAt
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
fileIds | [UUID!]! | required | List of file IDs to fetch |
Edges#
edges#
Query relationships between entities.
query {
edges(eventIds: ["evt-uuid-1", "evt-uuid-2"], edgeType: "sent_by") {
id
edgeType
sourceNodeId
sourceNodeType
targetNodeId
targetNodeType
extraData
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
eventIds | [UUID!] | null | Filter by source event IDs |
sourceNodeId | UUID | null | Filter by source node |
sourceNodeType | String | null | Filter by source type ("event", "user", "identity") |
targetNodeId | UUID | null | Filter by target node |
targetNodeType | String | null | Filter by target type |
edgeType | String | null | Filter by edge type (e.g., "sent_by", "participant") |
limit | Int | 100 | Maximum to return |
offset | Int | 0 | Offset for pagination |
createEdge#
Create a relationship between two entities. Requires events:write scope.
mutation {
createEdge(
sourceNodeId: "event-uuid"
sourceNodeType: "event"
targetNodeId: "user-uuid"
targetNodeType: "user"
edgeType: "relates_to"
extraData: { note: "custom data" }
) {
id
edgeType
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
sourceNodeId | UUID! | required | Source entity ID |
sourceNodeType | String! | required | Source type: "event", "user", "identity" |
targetNodeId | UUID! | required | Target entity ID |
targetNodeType | String! | required | Target type: "event", "user", "identity", "file" |
edgeType | String! | required | Relationship type |
extraData | JSON | null | Optional metadata on the edge |
Link Codes#
generateLinkCode#
Generate a code that links two user accounts across platforms.
mutation {
generateLinkCode(
identityId: "identity-uuid"
maxUses: 1
expiryMinutes: 15
sendToUser: true
) {
id
payload
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
identityId | UUID! | required | Identity generating the code |
maxUses | Int | 1 | Maximum redemptions (0 = unlimited) |
expiryMinutes | Int | 15 | Minutes until expiration |
sendToUser | Boolean | false | Send the code to the user via their platform |
redeemLinkCode#
Redeem a link code to merge two user accounts.
mutation {
redeemLinkCode(code: "1234-5678-9012-3456", identityId: "identity-uuid") {
id
payload
}
}Logs#
logs#
Query logs for your application. Requires logs:read scope.
query {
logs(eventId: "event-uuid", logLevel: "error", limit: 50) {
id
logLevel
logType
message
payload
createdAt
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
eventId | UUID | null | Filter by associated event |
eventIds | [UUID!] | null | Filter by multiple events |
logLevel | String | null | Filter by level: "debug", "info", "warning", "error" |
logType | String | null | Filter by type (e.g., "execution", "webhook_error") |
limit | Int | 100 | Maximum to return |
offset | Int | 0 | Offset for pagination |
createLog#
Create a log entry. Requires logs:write scope.
mutation {
createLog(
message: "Processing completed"
logLevel: "info"
logType: "execution"
payload: { duration_ms: 42 }
eventId: "event-uuid"
) {
id
createdAt
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
message | String! | required | Log message |
logLevel | String | "info" | Log level |
logType | String | "execution" | Log category |
payload | JSON | null | Structured data |
eventId | UUID | null | Associated event ID |
Stats#
stats#
Unified time-series stats query for events, webhooks, API keys, and more.
query {
stats(
sourceType: EVENT_TYPE
startDate: "2024-01-01T00:00:00Z"
endDate: "2024-02-01T00:00:00Z"
granularity: DAILY
entityId: "message.inbound"
) {
timestamp
count
entityId
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
sourceType | StatsSourceType! | required | Data source (see enum below) |
startDate | DateTime! | required | Start of date range |
endDate | DateTime! | required | End of date range |
granularity | StatsGranularity! | required | Time bucket size (see enum below) |
entityId | String | null | Filter by entity; when null, groups by entity |
userId | String | null | Filter events by user (EVENT source only) |
eventTypes | [String!] | null | Filter by event types (EVENT source only) |
conversationId | String | null | Filter by conversation (EVENT source only) |
getStats#
Get aggregate counts for the current application.
query {
getStats(timeframe: LAST_30_DAYS) {
identitiesCount
messagesCount
usersCount
}
}| Parameter | Type | Description |
|---|---|---|
timeframe | Timeframe! | ALL_TIME or LAST_30_DAYS |
Enums#
PlatformName#
enum PlatformName {
WHATSAPP
INSTAGRAM
FACEBOOK
TELEGRAM
SLACK
DISCORD
TWILIO
EMAIL
IMESSAGE
CUSTOM
UNKNOWN
}StatsSourceType#
enum StatsSourceType {
EVENT
EVENT_TYPE
API_KEY
PLATFORM_CONNECTION
WEBHOOK
USER
APPLICATION
}StatsGranularity#
enum StatsGranularity {
ONE_MINUTE
FIVE_MINUTE
FIFTEEN_MINUTE
HOURLY
SIX_HOURLY
DAILY
WEEKLY
}Timeframe#
enum Timeframe {
LAST_30_DAYS
ALL_TIME
}Pagination#
Outeract uses cursor-based pagination following the Relay specification.
Forward Pagination#
query {
events(first: 20, after: "cursor_xyz") {
edges {
node { id }
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}Backward Pagination#
query {
events(last: 20, before: "cursor_xyz") {
edges {
node { id }
cursor
}
pageInfo {
hasPreviousPage
startCursor
}
}
}Idempotency#
sendMessage and createEvent accept an optional idempotencyKey argument so
retries are safe. A client that times out and retries a mutation with the same
key gets the original event back instead of sending a second message or
creating a duplicate event.
mutation {
sendMessage(
message: "Your order has shipped"
recipientUserId: "user-uuid"
idempotencyKey: "b3c5a7de-8f21-4f6e-9c44-1a2b3c4d5e6f"
) {
id
}
}Semantics:
- Retrying with the same key in the same application returns the original event, with no second platform send and no duplicate webhook delivery to subscribers.
- Keys must be non-blank and at most 255 characters.
- Keys are scoped per application: the same key in two different applications creates two independent events.
- Keys share one namespace across
sendMessageandcreateEvent. Reusing a key with a different mutation or a different event type is rejected with an error rather than replaying an unrelated event. - Keys are unique per application indefinitely; they do not expire. Reusing an old key returns the old event, so generate a fresh UUID for each logical operation rather than deriving keys from request content.
- The key is stored in the event payload as
idempotency_key, which is a reserved field:createEventrejects a payload carrying one unless it exactly matches theidempotencyKeyargument (a payload-only key with the argument omitted is also rejected), andupdateEventcannot inject, change, or remove it. - A failed platform send does not consume the key. The failed attempt is recorded as an event without the key, and retrying with the same key performs a new send.
- Uniqueness is enforced by a database constraint, so concurrent retries with the same key still result in a single event.
Note: if the server crashes between the platform accepting a message and the event committing, a retry can re-send. This window is small; the key still prevents the common failure mode of client timeouts and network retries.
Error Handling#
GraphQL errors are returned in the errors array:
{
"data": null,
"errors": [
{
"message": "Platform connection not found",
"locations": [{"line": 2, "column": 3}],
"path": ["sendMessage"]
}
]
}Batching#
Combine multiple queries in one request:
query BatchQuery {
events(first: 10) {
edges { node { id } }
}
users(first: 10) {
edges { node { id } }
}
}