Skip to main content

GraphQL API

SolidInvoice's GraphQL API gives you a flexible, typed interface to the same data as the REST API. Instead of calling multiple fixed endpoints, you write a single query that describes exactly what you need โ€” and the server returns precisely that, nothing more.

The GraphQL endpoint is available at /api/graphql on your SolidInvoice installation.

Hosted vs self-hosted

If you're on the hosted SolidInvoice plan, the endpoint is:

https://solidinvoice.app/api/graphql

For a self-hosted instance, replace the domain with your own:

https://your-domain.example/api/graphql
tip

Not sure whether to use REST or GraphQL? Use REST if you're integrating with automation tools like Zapier or n8n, or calling simple individual resources. Use GraphQL when you need to fetch related data in one request, or want fine-grained control over the response shape.

Interactive explorer (GraphiQL)โ€‹

Opening /api/graphql in a browser loads GraphiQL, an in-browser IDE for building and testing queries. On the hosted plan that's solidinvoice.app/api/graphql; on a self-hosted instance use your own domain. It includes:

  • A query editor with syntax highlighting and autocomplete
  • Inline documentation for every type and field
  • A history panel showing your recent queries
  • Variable and header editors

GraphiQL is the fastest way to explore what's available โ€” use the Docs panel on the right to browse all types, queries, and mutations.

Authenticationโ€‹

GraphQL uses the same API token authentication as the REST API. Create a token at Settings โ†’ API Keys (see Creating an API token), then send it in the X-API-TOKEN header on every request.

curl -X POST https://solidinvoice.app/api/graphql \
-H "X-API-TOKEN: <your-token>" \
-H "Content-Type: application/json" \
-d '{"query": "{ invoices { edges { node { id status } } } }"}'

In GraphiQL, add the header under the Headers tab at the bottom of the editor:

{
"X-API-TOKEN": "<your-token>"
}

Requests without a valid token receive a 401 Unauthorized response.

info

Tokens are scoped to one user and one company. If your account has multiple companies, generate a separate token for each by switching companies before creating the token.

Querying dataโ€‹

GraphQL queries are sent as HTTP POST requests to /api/graphql with a JSON body containing a query field.

Fetching a collectionโ€‹

Use the plural resource name to fetch a list. Each collection returns a Relay-style connection with an edges wrapper:

query {
invoices {
edges {
node {
id
status
total
}
}
}
}
curl -X POST https://solidinvoice.app/api/graphql \
-H "X-API-TOKEN: <your-token>" \
-H "Content-Type: application/json" \
-d '{
"query": "{ invoices { edges { node { id status total } } } }"
}'

Fetching a single itemโ€‹

Use the singular resource name with an id argument. The ID must be the full IRI string (e.g. /api/invoices/01J...):

query {
invoice(id: "/api/invoices/01JDKR4XQ3NEVF8CNKQSJ5GPRT") {
id
status
total
client {
name
}
}
}

One of GraphQL's key advantages is requesting related resources in a single round-trip. The following query fetches invoices together with their client name and line items in one request:

query {
invoices {
edges {
node {
id
status
total
client {
name
currency
}
lines {
edges {
node {
description
qty
price
}
}
}
}
}
}
}

Filtering collectionsโ€‹

Pass filter arguments directly to the collection query. The available filters match those on the REST API for each resource.

Filter invoices by statusโ€‹

query {
invoices(status: "pending") {
edges {
node {
id
status
total
}
}
}
}

Filter clients by nameโ€‹

query {
clients(name: "Acme") {
edges {
node {
id
name
}
}
}
}

Using variablesโ€‹

For dynamic queries, pass filter values as GraphQL variables rather than inlining them:

query GetInvoicesByStatus($status: String) {
invoices(status: $status) {
edges {
node {
id
status
total
}
}
}
}

Send the variables in the variables field of the request body:

curl -X POST https://solidinvoice.app/api/graphql \
-H "X-API-TOKEN: <your-token>" \
-H "Content-Type: application/json" \
-d '{
"query": "query GetInvoicesByStatus($status: String) { invoices(status: $status) { edges { node { id status total } } } }",
"variables": { "status": "pending" }
}'

Mutationsโ€‹

Mutations create, update, or delete resources. They follow a consistent naming pattern:

OperationMutation name patternExample
Createcreate{Resource}createClient
Updateupdate{Resource}updateInvoice
Deletedelete{Resource}deleteQuote

Creating a resourceโ€‹

Pass the fields in an input argument. The mutation returns the created resource:

mutation {
createClient(input: {
name: "Acme Corp"
currency: "USD"
website: "https://acme.example"
}) {
client {
id
name
}
}
}

Updating a resourceโ€‹

Provide the id (full IRI) and only the fields you want to change:

mutation {
updateClient(input: {
id: "/api/clients/01JDKR4XQ3NEVF8CNKQSJ5GPRT"
website: "https://new-site.example"
}) {
client {
id
website
}
}
}

Deleting a resourceโ€‹

mutation {
deleteInvoice(input: {
id: "/api/invoices/01JDKR4XQ3NEVF8CNKQSJ5GPRT"
}) {
invoice {
id
}
}
}
warning

Deletion is immediate and cannot be undone through the API. Make sure you have the correct id before running a delete mutation.

Paginationโ€‹

GraphQL collections use cursor-based pagination via the Relay connection spec. Each collection query accepts first, last, before, and after arguments, and returns pageInfo alongside the edges:

query {
invoices(first: 10, after: "cursor-value-from-previous-page") {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges {
cursor
node {
id
status
total
}
}
}
}

To page forward through results:

  1. Run the query without after to get the first page.
  2. Check pageInfo.hasNextPage. If true, pass pageInfo.endCursor as the after argument in your next request.
  3. Repeat until hasNextPage is false.

The default page size is 30 items. Pass a first argument to request fewer (maximum 30 per page):

query {
invoices(first: 5) {
edges {
node { id status }
}
}
}

Available resourcesโ€‹

All core resources are available via GraphQL. API token management is REST-only and cannot be accessed through the GraphQL API.

ResourceQuery (collection)Query (single)Mutations
Clientsclientsclient(id:)createClient, updateClient, deleteClient
Contactscontactscontact(id:)createContact, updateContact, deleteContact
Addressesaddressesaddress(id:)createAddress, updateAddress, deleteAddress
Invoicesinvoicesinvoice(id:)createInvoice, updateInvoice, deleteInvoice
Invoice linesinvoiceLinesinvoiceLine(id:)createInvoiceLine, updateInvoiceLine, deleteInvoiceLine
Recurring invoicesrecurringInvoicesrecurringInvoice(id:)createRecurringInvoice, updateRecurringInvoice, deleteRecurringInvoice
Quotesquotesquote(id:)createQuote, updateQuote, deleteQuote
Quote linesquoteLinesquoteLine(id:)createQuoteLine, updateQuoteLine, deleteQuoteLine
Paymentspaymentspayment(id:)createPayment
Taxestaxestax(id:)createTax, updateTax, deleteTax
info

Monetary amounts (totals, prices, balances) are always integers in the smallest currency unit โ€” cents for USD/EUR, pence for GBP, etc. For example, 1000 represents $10.00. The currency itself comes from the associated client.

Introspectionโ€‹

GraphQL's introspection system lets you query the schema itself to discover all available types, fields, and operations. GraphiQL uses introspection automatically, but you can also query it directly:

query {
__schema {
types {
name
kind
}
}
}

To inspect a specific type:

query {
__type(name: "Invoice") {
fields {
name
type {
name
kind
}
}
}
}

Troubleshootingโ€‹

401 Unauthorizedโ€‹

The X-API-TOKEN header is missing, incorrect, or the token has been revoked. Verify the header name and value โ€” the header must be X-API-TOKEN (not Authorization or Bearer). If the token no longer works, generate a new one from Settings โ†’ API Keys.

Query returns null for a resourceโ€‹

The resource either doesn't exist, was deleted, or the token's company doesn't own it. Tokens are company-scoped โ€” if you have multiple companies, make sure the token was created while the correct company was active.

Mutation fails with a validation errorโ€‹

Check the errors array in the response. Each error includes a message and an extensions.violations array listing the specific field that failed validation and why:

{
"errors": [{
"message": "name: This value should not be blank.",
"extensions": {
"violations": [
{ "path": "name", "message": "This value should not be blank." }
]
}
}]
}

GraphiQL shows a blank page or won't loadโ€‹

GraphiQL is served at /api/graphql and requires a browser. If you're getting a blank page, check your browser console for JavaScript errors and make sure the page is not being blocked by a content security policy on your SolidInvoice instance.

API token management operations failโ€‹

API token management (listing, creating, and revoking tokens) is not available via GraphQL โ€” use the REST API or the Settings โ†’ API Keys page in the UI instead.