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.
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
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.
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
}
}
}
Fetching related dataโ
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:
| Operation | Mutation name pattern | Example |
|---|---|---|
| Create | create{Resource} | createClient |
| Update | update{Resource} | updateInvoice |
| Delete | delete{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
}
}
}
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:
- Run the query without
afterto get the first page. - Check
pageInfo.hasNextPage. Iftrue, passpageInfo.endCursoras theafterargument in your next request. - Repeat until
hasNextPageisfalse.
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.
| Resource | Query (collection) | Query (single) | Mutations |
|---|---|---|---|
| Clients | clients | client(id:) | createClient, updateClient, deleteClient |
| Contacts | contacts | contact(id:) | createContact, updateContact, deleteContact |
| Addresses | addresses | address(id:) | createAddress, updateAddress, deleteAddress |
| Invoices | invoices | invoice(id:) | createInvoice, updateInvoice, deleteInvoice |
| Invoice lines | invoiceLines | invoiceLine(id:) | createInvoiceLine, updateInvoiceLine, deleteInvoiceLine |
| Recurring invoices | recurringInvoices | recurringInvoice(id:) | createRecurringInvoice, updateRecurringInvoice, deleteRecurringInvoice |
| Quotes | quotes | quote(id:) | createQuote, updateQuote, deleteQuote |
| Quote lines | quoteLines | quoteLine(id:) | createQuoteLine, updateQuoteLine, deleteQuoteLine |
| Payments | payments | payment(id:) | createPayment |
| Taxes | taxes | tax(id:) | createTax, updateTax, deleteTax |
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.