GraphQL API Documentation
Schema Organization
The GraphQL schema is organized by resource in a modular structure for better maintainability and discoverability:
graphql/
├── schema.graphql # Root schema with imports
├── shared/
│ ├── interfaces.graphql # Common interfaces (Node, Timestamped)
│ └── errors.graphql # Error types (ValidationError)
├── user/
│ ├── types.graphql # User type definition
│ ├── queries.graphql # user, users queries
│ ├── mutations.graphql # createUser, updateUser
│ └── inputs.graphql # User inputs and payloads
├── party/
│ ├── types.graphql # Party, PartyType, PartySubtype, PartyIdentifier
│ ├── queries.graphql # party, parties, partyTypes, partySubtypes
│ ├── mutations.graphql # createParty, updateParty, subscribePartyToProduct
│ └── inputs.graphql # Party inputs and payloads
├── product/
│ ├── types.graphql # Product, ProductAccount, PartyProduct
│ ├── queries.graphql # products
│ └── inputs.graphql # Product inputs (future)
└── party-group/
├── types.graphql # PartyGroup, PartyGroupMember
├── queries.graphql # partyGroup, partyGroups
├── mutations.graphql # createPartyGroup, addPartyToGroup
└── inputs.graphql # Party group inputs and payloads
Design Principles
- Modular by Resource: Each business domain (User, Party, Product, etc.) has its own folder
- Consistent Naming: Generic filenames (types.graphql, queries.graphql, mutations.graphql, inputs.graphql)
- Shared Interfaces: Common patterns like
NodeandTimestampedare defined once and reused - Self-Documenting: All types and fields include descriptive comments
Reference Data Queries
Reference data types (PartyType, PartySubtype, Product) are stored in the database and can be queried dynamically. This allows the API to discover valid values without hardcoding them in validation rules.
Query Available Party Types
query GetPartyTypes {
partyTypes {
data {
id
name
description
}
}
}
Example Response:
{
"data": {
"partyTypes": {
"data": [
{
"id": 1,
"name": "person",
"description": "An individual human"
},
{
"id": 2,
"name": "organization",
"description": "A company, office, or group"
},
{
"id": 3,
"name": "system",
"description": "An external system (MLS, API, etc.)"
}
]
}
}
}
Query Party Subtypes
query GetPartySubtypes {
partySubtypes {
data {
id
name
description
partyType {
name
}
}
}
}
# Or filter by party type
query GetSubtypesForPerson {
partySubtypes(party_type_id: 1) {
data {
name
description
}
}
}
Query Products
query GetProducts {
products {
data {
id
name
}
}
}
Pagination Strategy
The API uses a hybrid pagination approach that balances consistency with pragmatism:
- Reference Data (products, partyTypes, partySubtypes): Default
first: 100to effectively load all items - Transactional Data (users, parties, partyGroups): Default
first: 10for standard pagination
All queries support pagination parameters:
first: Number of items to return (defaults vary by query type)page: Page number to retrieve
Example:
query GetParties {
parties(first: 25, page: 2) {
data {
id
name
}
paginatorInfo {
currentPage
lastPage
total
hasMorePages
}
}
}
Validation
Input validations are database-driven where possible:
party_type: Validated againstparty_types.namein databaseparty_subtype: Validated againstparty_subtypes.namein databaseproduct: Validated againstproducts.namein database
This ensures the API accepts any valid values added to the database without requiring schema changes.
Example Queries
Get All Users
query GetAllUsers {
users(first: 10, page: 1) {
data {
id
email
auth0_sub
party {
id
name
first_name
last_name
}
created_at
updated_at
}
paginatorInfo {
currentPage
lastPage
total
hasMorePages
}
}
}
Get a Single User
query GetUser {
user(id: "a094247a-6885-47dc-86b5-d8df3d8988dd") {
id
email
auth0_sub
party {
name
}
}
}
query GetUserFromSub {
user(auth0_sub: "auth0|jason-auth0-sub") {
id
email
auth0_sub
party {
name
}
}
}
query GetUserFromEmail {
user(email: "jason.welch@elmstreet.com") {
id
email
auth0_sub
party {
name
}
}
}
Update a User
mutation UpdateUser {
updateUser(
id: "a094247a-6885-47dc-86b5-d8df3d8988dd"
input: { email: "jason.welch@elmstreet.com" }
) {
user {
id
email
auth0_sub
party {
id
}
}
success
errors {
field
message
}
}
}
Create a Party
First, discover available party types and subtypes:
query {
partyTypes {
data {
name
description
}
}
partySubtypes {
data {
name
description
partyType {
name
}
}
}
}
Then create the party:
mutation {
createParty(
input: {
party_type: "person"
party_subtype: "agent"
first_name: "Lee"
last_name: "Ralls"
user: {
auth0_sub: "auth0|sub-for-lee-raalls"
email: "lee.ralls@elmstreet.com"
}
}
) {
party {
id
first_name
last_name
partyType {
name
}
partySubtype {
name
}
user {
id
email
auth0_sub
}
}
success
errors {
field
message
}
}
}
Subscribe a Party to a Product
mutation SubscribeParty {
subscribePartyToProduct(
partyId: "a0d46520-92e6-41b6-860a-c1df703606a9"
input: { product: "IDX Broker", account_id: "12345", role: "owner" }
) {
partyProduct {
id
role
product {
name
}
party {
id
}
}
success
errors {
field
message
}
}
}
Get a Party with Products
query GetParty {
party(id: "a0d46520-92e6-41b6-860a-c1df703606a9") {
id
name
partyType {
id
name
}
partyProducts {
role
product {
name
}
productAccount {
product_account_id
product_account_id_type
}
}
}
}
List Parties with Filtering
query GetFilteredParties {
parties(
first: 20
party_type_id: 1
name: "Smith"
orderBy: [{ column: "last_name", order: ASC }]
) {
data {
id
name
first_name
last_name
partyType {
name
}
}
paginatorInfo {
total
hasMorePages
}
}
}
Create a Party Group
mutation CreateGroup {
createPartyGroup(
input: {
name: "Elite Team"
description: "Top performing agents"
created_by_party_id: "a0d46520-92e6-41b6-860a-c1df703606a9"
}
) {
partyGroup {
id
name
description
createdBy {
name
}
}
success
errors {
field
message
}
}
}
Add Party to Group
mutation AddMember {
addPartyToGroup(
input: {
party_group_id: "b1d46520-92e6-41b6-860a-c1df703606a9"
party_id: "a0d46520-92e6-41b6-860a-c1df703606a9"
role: "member"
status: "active"
}
) {
partyGroupMember {
id
role
status
party {
name
}
partyGroup {
name
}
}
success
errors {
field
message
}
}
}
Error Handling
All mutations return a consistent payload structure:
type CreatePartyPayload {
party: Party # The created entity (null if errors)
success: Boolean! # Whether the operation succeeded
errors: [ValidationError!]! # Validation errors (empty if success)
}
type ValidationError {
field: String # Field that caused the error (if field-specific)
message: String! # Human-readable error message
}
Example Error Response:
{
"data": {
"createParty": {
"party": null,
"success": false,
"errors": [
{
"field": "party_type",
"message": "The selected party_type is invalid."
}
]
}
}
}
Best Practices
- Query Reference Data First: Before creating parties or products, query
partyTypesandpartySubtypesto discover valid values - Use Pagination: All list queries support pagination - specify
firstandpageparameters - Request Only What You Need: GraphQL allows you to request exactly the fields you need - this improves performance
- Check Success Field: Always check the
successfield in mutation responses before using the returned entity - Handle Errors: Check the
errorsarray for validation issues and display them to users
Schema Validation
To validate the schema after making changes:
php artisan lighthouse:validate-schema
GraphiQL
Access the interactive GraphQL explorer at /graphiql to explore the schema, run queries, and see documentation.