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:
| Field | Purpose |
|---|---|
viewer | The 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:
| Field | Role |
|---|---|
id | Client-generated UUID for the record being created or updated. Generate a UUID v7 when creating. |
clientMutationId | Optional client correlation id; echoed on the payload |
mutationMetadata | Optional JSON the product uses for client sync metadata. You can omit it. |
now | Optional 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 areISO8601DateTime. - Rich text fields end in
RichTextand store HTML. Empty values are blank HTML such as<p></p>. - JSON fields are the
JSONscalar.
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.