GraphQL

GraphQL

POST /graphql with a Bearer token. Query from organization and project. Mutate through nested organization and project fields.

The GraphQL endpoint is:

POST https://api.constructable.ai/graphql

Send JSON:

{
  "query": "query($id: ID!) { organization(id: $id) { id name } }",
  "variables": { "id": "019e..." },
  "operationName": null
}

The HTTP body can also use form fields named query, variables, and operationName. Introspection is available to authenticated clients. You can download the committed schema from /api-docs/schema.graphql.

Root fields

Query has two fields besides the internal constructableUrl:

FieldPurpose
viewerThe signed-in user id and the organization id bound to the token
organization(id: ID!)The organization the user can access, or null

There is no root project field. Load a project from the organization:

query Project($organizationId: ID!, $projectId: ID!) {
  organization(id: $organizationId) {
    id
    name
    project(id: $projectId) {
      id
      name
      number
    }
  }
}

Lists on Organization and Project are Relay connections. See Pagination.

Nested mutations

Mutation is also nested:

type Mutation {
  organization(id: ID!): OrganizationMutations!
  user: UserMutations!
}

OrganizationMutations includes organization-level writes and a project(id:) field that returns ProjectMutations. Project records are written like this:

mutation CreateDraftRfi(
  $organizationId: ID!
  $projectId: ID!
  $input: CreateDraftRfiInput!
) {
  organization(id: $organizationId) {
    project(id: $projectId) {
      createDraftRfi(input: $input) {
        rfi {
          id
          subject
          status
        }
        errors
      }
    }
  }
}

If the organization or project does not exist, or the user cannot read the project, the mutation returns a GraphQL error and HTTP 422.

User-scoped operations (avatar, push devices, notification preferences) hang off user. They are available to session tokens only. Organization API OAuth tokens do not expose the user mutation field.

mutation SetMyTimeZone($input: SetMyTimeZoneInput!) {
  user {
    setMyTimeZone(input: $input) {
      errors
    }
  }
}

Mutation inputs

Most writes are Relay-style mutations with a single input argument.

Common input fields:

FieldRole
idClient-generated UUID for the record being created or updated. Generate a UUID v7 when creating.
clientMutationIdOptional client correlation id; echoed on the payload
mutationMetadataOptional JSON the product uses for client sync metadata. You can omit it.
nowOptional timestamp so clients can keep optimistic clocks aligned. The server uses current time when omitted.

Payloads always include errors: [String!]!. An empty array means the write succeeded. Check errors even when HTTP status is 200 and data is present.

Payloads also include constructableUrl for the in-app deep link when one exists.

Money, dates, and rich text

  • Money fields are integer cents (amountCents, costCents, BigInt).
  • Dates are ISO8601Date (YYYY-MM-DD). Datetimes are ISO8601DateTime.
  • Rich text fields end in RichText and store HTML. Empty values are blank HTML such as <p></p>.
  • JSON fields are the JSON scalar.

Record lookup

On a parent type, a plural field is a connection (rfis, submittals) and the singular field takes id (rfi(id:), submittal(id:)). Both are permission-scoped. Unknown or unauthorized ids resolve to null rather than throwing, for these lookups.

Introspection and the reference

Authenticated clients can run GraphQL introspection. The reference is generated from the same committed schema the server uses. Search it by type or field name instead of guessing.