GraphQL API#

The primary API for interacting with Outeract. Every operation is a GraphQL query or mutation against a single endpoint.

Endpoints#

EndpointAuthDescription
POST /API KeyDeveloper 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/json

Note: API keys are already scoped to a specific application, so no X-Outeract-App-ID header 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
    }
  }
}
ParameterTypeDefaultDescription
firstInt-Number of events (forward pagination)
lastInt-Number of events (backward pagination)
afterStringnullCursor for forward pagination
beforeStringnullCursor for backward pagination
eventTypes[String!]nullFilter by event type names
eventIds[UUID!]nullFetch specific events by ID
userIdUUIDnullFilter events linked to a user
conversationIdUUIDnullFilter events in a conversation
originEventIdUUIDnullFilter by parent event
relatedToRelationFilternullFilter by relationships to other events
relatedNodeIdUUIDnullOnly 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
relatedNodeTypeStringnullWith relatedNodeId, require the edge’s target node type (e.g. "user"). Ignored without relatedNodeId
relatedEdgeTypeStringnullWith relatedNodeId, require the edge’s edge type (e.g. "sent_to", "participant"). Ignored without relatedNodeId
payloadFilters[PayloadFilter!]nullFilter by payload field values
sinceStringnullISO datetime; only events after this time
minimalBooleanfalseReturn minimal event data
includeTimingBooleanfalseInclude 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
  }
}
ParameterTypeDefaultDescription
userIdUUID!requiredUser ID to associate the event with
payloadJSON!requiredEvent payload data
eventTypeStringnullEvent type name (e.g., "order.created")
eventTypeIdUUIDnullEvent schema ID (alternative to eventType)
originEventIdUUIDnullParent event ID for event chains
edges[JSON!]nullEdges to create with the event
idempotencyKeyStringnullClient-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
  }
}
ParameterTypeDefaultDescription
eventIdUUID!requiredEvent ID to update
payloadJSONnullNew payload data
mergePayloadBooleantrueIf 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.

ParameterTypeDefaultDescription
userIdUUID!requiredUser whose events to delete
beforeDateTimenullOnly delete events before this time
sinceDateTimenullOnly 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
    }
  }
}
ParameterTypeDefaultDescription
first / lastInt-Number of users to return
after / beforeStringnullCursor for pagination
searchStringnullSearch by name, user ID, external ID, or identity ID
orderByString"created_at""created_at" or "last_active_at"
userIds[UUID!]nullFetch specific users by ID (bypasses pagination)
configFilters[ConfigFilter!]nullFilter 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 }
  }
}
ParameterTypeDefaultDescription
identityIds[UUID!]nullFetch specific identities by ID
externalIds[String!]nullFetch by external IDs
userIdUUIDnullFilter by user
identityTypeStringnullFilter by type (e.g., "whatsapp", "slack")
limitInt100Maximum to return
offsetInt0Offset for pagination

createUser#

mutation CreateUser($name: String, $isSystemUser: Boolean) {
  createUser(name: $name, isSystemUser: $isSystemUser) {
    id
    name
    isSystemUser
    createdAt
  }
}
ParameterTypeDefaultDescription
nameStringnullDisplay name
isSystemUserBooleanfalseWhether 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 }
  }
}
ParameterTypeDefaultDescription
userIdUUID!requiredUser to attach the identity to
externalIdString!requiredExternal identifier (phone number, username, etc.)
identityTypeString!requiredPlatform or custom type (e.g., "whatsapp", "slack", "phone")
configJSONnullAdditional 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 }
  }
}
ParameterTypeDefaultDescription
sourceUserIdUUID!requiredUser to transfer identities from
targetUserIdUUID!requiredUser to transfer identities to
deleteSourceUserBooleantrueDelete the source user after transfer
deleteSourceConversationsBooleantrueDelete 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 }
  }
}
ParameterTypeDefaultDescription
firstInt25Number of conversations to return
afterStringnullCursor for pagination
searchStringnullFilter 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 }
  }
}
ParameterTypeDefaultDescription
userIdUUID!requiredUser ID
limitInt50Maximum conversations to return

deleteConversations#

mutation {
  deleteConversations(conversationIds: ["conv-uuid-1"], deleteMessages: true)
}

Returns Int, the number of conversations deleted.

ParameterTypeDefaultDescription
conversationIds[UUID!]!requiredConversation IDs to delete
deleteMessagesBooleanfalseAlso 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
  }
}
ParameterTypeDefaultDescription
messageStringnullText content
recipientUserIdUUIDnullRecipient user ID
conversationIdUUIDnullSend to an existing conversation
senderUserIdUUIDnullOverride sender (defaults to system user)
platformPlatformNamenullTarget platform
platformConnectionIdUUIDnullSpecific platform connection to use
fileIds[UUID!]nullFile attachments
templateJSONnullTemplate message (see below)
idempotencyKeyStringnullClient-supplied key for safe retries; see Idempotency

Template structure:

FieldTypeDescription
nameString (required)Template name as registered with the platform
languageString (required)Language code (e.g. "en", "en_US")
componentsArrayTemplate 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
  }
}
ParameterTypeDefaultDescription
fileIds[UUID!]!requiredList 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
  }
}
ParameterTypeDefaultDescription
eventIds[UUID!]nullFilter by source event IDs
sourceNodeIdUUIDnullFilter by source node
sourceNodeTypeStringnullFilter by source type ("event", "user", "identity")
targetNodeIdUUIDnullFilter by target node
targetNodeTypeStringnullFilter by target type
edgeTypeStringnullFilter by edge type (e.g., "sent_by", "participant")
limitInt100Maximum to return
offsetInt0Offset 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
  }
}
ParameterTypeDefaultDescription
sourceNodeIdUUID!requiredSource entity ID
sourceNodeTypeString!requiredSource type: "event", "user", "identity"
targetNodeIdUUID!requiredTarget entity ID
targetNodeTypeString!requiredTarget type: "event", "user", "identity", "file"
edgeTypeString!requiredRelationship type
extraDataJSONnullOptional metadata on the edge

generateLinkCode#

Generate a code that links two user accounts across platforms.

mutation {
  generateLinkCode(
    identityId: "identity-uuid"
    maxUses: 1
    expiryMinutes: 15
    sendToUser: true
  ) {
    id
    payload
  }
}
ParameterTypeDefaultDescription
identityIdUUID!requiredIdentity generating the code
maxUsesInt1Maximum redemptions (0 = unlimited)
expiryMinutesInt15Minutes until expiration
sendToUserBooleanfalseSend 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
  }
}
ParameterTypeDefaultDescription
eventIdUUIDnullFilter by associated event
eventIds[UUID!]nullFilter by multiple events
logLevelStringnullFilter by level: "debug", "info", "warning", "error"
logTypeStringnullFilter by type (e.g., "execution", "webhook_error")
limitInt100Maximum to return
offsetInt0Offset 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
  }
}
ParameterTypeDefaultDescription
messageString!requiredLog message
logLevelString"info"Log level
logTypeString"execution"Log category
payloadJSONnullStructured data
eventIdUUIDnullAssociated 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
  }
}
ParameterTypeDefaultDescription
sourceTypeStatsSourceType!requiredData source (see enum below)
startDateDateTime!requiredStart of date range
endDateDateTime!requiredEnd of date range
granularityStatsGranularity!requiredTime bucket size (see enum below)
entityIdStringnullFilter by entity; when null, groups by entity
userIdStringnullFilter events by user (EVENT source only)
eventTypes[String!]nullFilter by event types (EVENT source only)
conversationIdStringnullFilter by conversation (EVENT source only)

getStats#

Get aggregate counts for the current application.

query {
  getStats(timeframe: LAST_30_DAYS) {
    identitiesCount
    messagesCount
    usersCount
  }
}
ParameterTypeDescription
timeframeTimeframe!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 sendMessage and createEvent. 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: createEvent rejects a payload carrying one unless it exactly matches the idempotencyKey argument (a payload-only key with the argument omitted is also rejected), and updateEvent cannot 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 } }
  }
}