MSP Partner API

Read your MSP Networks, Customer Networks, and license counts, and create new MSP Portals, with a GraphQL API.

Overview

The MSP Partner API is a GraphQL API for Twingate resellers and managed service providers. It works across every network you manage, so you can:

  • List all of your MSP Portals and Customer Networks in one call.
  • Pull the plan and license count for each Customer Network into your billing, PSA, or CRM.
  • Create a new MSP Portal and invite its first admin from your own onboarding flow.

The API has three queries and one mutation. It cannot update or delete anything, and it cannot create Customer Networks. Create Customer Networks in the MSP Portal. They then appear in the API automatically.

Concepts

ConceptDescription
PartnerYour organization. The API key belongs to the Partner and can see everything under it.
MSP NetworkAn MSP Portal. It has its own admins and its own Admin Console URL (https://<slug>.twingate.com). A Partner can have many MSP Networks.
Customer NetworkOne customer’s Twingate network, managed from an MSP Network. See Customer Networks.
SubscriptionA plan type and a license count. Each Customer Network has one. Each MSP Network reports a total per plan type across its Customer Networks.

Getting started

Credentials

Every request needs two headers. Both values are issued by Twingate. To request them, contact partnersupport@twingate.com.

HeaderValueNotes
X-PARTNER-IDYour Partner IDIdentifies your Partner. Not secret.
X-API-KEYYour Partner API keySecret. Shown once when issued. If you lose it, request a new key.

Endpoint

ItemValue
URLIssued with your credentials. Includes your region, for example https://api.us2.twingate.com/partners
ProtocolGraphQL over HTTPS. Send every request, including queries, as a POST with a JSON body. GET requests return HTTP 400.
HeadersX-PARTNER-ID, X-API-KEY, Content-Type: application/json

Call the API from server-side code only. It does not return CORS headers for third-party origins, so requests from a browser fail, and a key in browser code is exposed to anyone using the page.

We recommend setting a User-Agent header with your integration’s name and version, for example acme-psa-sync/2.1.0. It helps Twingate support find your requests.

First request

Use the partner query to check your credentials:

curl https://api.us2.twingate.com/partners \
-H 'Content-Type: application/json' \
-H "X-PARTNER-ID: $TWINGATE_PARTNER_ID" \
-H "X-API-KEY: $TWINGATE_PARTNER_API_KEY" \
-d '{"query":"{ partner { id name } }"}'

A successful request returns your Partner record:

{"data": {"partner": {"id": "UGFydG5lcjox", "name": "Example Reseller Inc."}}}

A 401 response means a header is missing or incorrect. See Troubleshooting.

GraphQL clients

GraphQL clients such as Altair, Postman, and Insomnia can send requests to the API. Set the URL to your API URL (for example https://api.us2.twingate.com/partners), add the X-PARTNER-ID and X-API-KEY headers, and load the schema for autocomplete. The schema is available in SDL and introspection formats from partnersupport@twingate.com, along with an integration playbook that includes sample queries, client code, and a local mock server.

Operations

OperationKindArgumentsReturns
partnerQueryNonePartner
networksQueryfilter, first, after, last, beforeNetworkConnection
networkQueryid: ID!Network (MSP or Customer) or null
mspNetworkCreateMutationnetworkSlug: String!, networkName: String!, adminEmail: String!MspNetworkCreateMutation!

partner

Returns the Partner that owns the API key. Use it to check credentials or as a health check.

{
partner {
id
name
createdAt
updatedAt
}
}

networks

Lists the MSP Networks and Customer Networks under your Partner. Disabled networks are excluded. Use filter to return one network type:

networks(filter: { networkType: { in: [MSP] } }) # MSP Networks only
networks(filter: { networkType: { in: [CUSTOMER_NETWORK] } }) # Customer Networks only
networks # Both types

networks returns a union of MspNetwork and CustomerNetwork. Select fields with inline fragments, and use __typename to tell the two apart:

{
networks(first: 50) {
edges {
node {
__typename
... on MspNetwork {
id
name
}
... on CustomerNetwork {
id
name
parent {
name
}
}
}
}
}
}

network

Returns one network by ID, or null if the ID does not belong to your Partner. An ID of another type, or a value that is not an ID, returns an error. Unlike networks, network also returns disabled networks.

query GetNetwork($id: ID!) {
network(id: $id) {
__typename
... on MspNetwork {
id
name
url
}
... on CustomerNetwork {
id
name
url
subscription {
planType
licenses
}
}
}
}

mspNetworkCreate

Creates a new MSP Network (MSP Portal) and invites its first admin.

ArgumentDescription
networkSlugBecomes the subdomain of the Admin Console URL. Twingate removes every character that is not a letter or number, including hyphens, and converts the rest to lowercase. Must be unused across all of Twingate after this conversion. Cannot be changed later.
networkNameThe display name in the MSP Portal.
adminEmailThe first admin and billing contact. Use a real mailbox, not an alias, that can sign in with Google, Microsoft, GitHub, or LinkedIn.
mutation {
mspNetworkCreate(
networkSlug: "acmemsp"
networkName: "Acme MSP"
adminEmail: "it@acme.com"
) {
ok
error
entity {
id
name
url
}
}
}

The new MSP Network is on the Business plan and uses social sign-in. The admin receives a welcome email and an invitation that requires email verification.

Validation errors return HTTP 200 with ok: false and a message in error, so always check ok:

{"data": {"mspNetworkCreate": {"ok": false, "error": "Network slug is taken.", "entity": null}}}
errorWhat to do
Network slug is taken.Choose another slug. Slugs are compared after conversion, so acme-msp and AcmeMSP both conflict with an existing acmemsp. If you are retrying a request, first check whether the earlier request succeeded.
Network slug is invalid.No letters or numbers were left after other characters were removed. Use lowercase letters and numbers.
Admin email is not a valid email address.Correct the email address.
MSP Network limit of 1000 has been reached or exceeded.Your Partner has reached 1,000 networks. Contact partnersupport@twingate.com.
Failed to create MSP NetworkTemporary failure. Wait, confirm the network does not exist, then try again.

Types

Partner

FieldTypeDescription
idID!The same value you send in X-PARTNER-ID.
nameString!The Partner name.
createdAtDateTime!ISO-8601 timestamp.
updatedAtDateTime!ISO-8601 timestamp.

MspNetwork

FieldTypeDescription
idID!
nameString!The display name in the MSP Portal.
urlString!The Admin Console URL, for example https://acme-msp.twingate.com.
aggregatedSubscriptions[Subscription!]!License totals across its active Customer Networks, one entry per plan type.
customerNetworksCustomerNetworkConnection!Active Customer Networks. Paginated.
adminUsersUserConnection!Active users with the Admin role. Paginated.

CustomerNetwork

FieldTypeDescription
idID!
nameString!
urlString!The Admin Console URL.
subscriptionSubscriptionnull until the plan is set, which can take a short time after the Customer Network is created.
parentMspNetwork!The MSP Network that manages this Customer Network.

Subscription

FieldTypeDescription
planTypePlanType!The plan.
licensesInt!Billable license count.

licenses is the number of active users plus active Service Accounts, minus active admins, with a minimum of 1. Admins are not billed, so licenses is the billable number. See MSP Billing.

User

FieldType
idID!
emailString!

Enums

EnumValues
NetworkTypeMSP, CUSTOMER_NETWORK
PlanTypeTEAMS, BUSINESS, BUSINESS_AND_DNSF (Business with DNS Filtering), ENTERPRISE

IDs

IDs are opaque strings. Store them as they are returned and use them as keys in your own systems. Do not decode or construct them. Match records on IDs, not names, because names can change.

Pagination

networks, customerNetworks, and adminUsers use Relay-style cursor pagination:

query ListCustomers($after: String) {
networks(filter: { networkType: { in: [CUSTOMER_NETWORK] } }, first: 50, after: $after) {
totalCount
pageInfo {
hasNextPage
endCursor
}
edges {
node {
... on CustomerNetwork {
id
name
subscription {
planType
licenses
}
parent {
id
name
}
}
}
}
}
}
  • Send the query with first: 50 and no after value.
  • Process edges.
  • If pageInfo.hasNextPage is true, send the query again with after set to pageInfo.endCursor.
  • Repeat until hasNextPage is false.

The server caps page sizes, so always loop on hasNextPage instead of assuming one page holds every result. totalCount is the number of matching items across all pages. Use last and before to page backwards.

Nested connections, such as customerNetworks inside networks, paginate separately. To list every Customer Network, use the flat networks query filtered to CUSTOMER_NETWORK shown above. It needs only one loop.

Common workflows

Portfolio dashboard

Show every MSP Network with its customer count, licenses per plan, and admins:

query ListMspNetworks($after: String) {
networks(filter: { networkType: { in: [MSP] } }, first: 50, after: $after) {
pageInfo {
hasNextPage
endCursor
}
edges {
node {
... on MspNetwork {
id
name
url
aggregatedSubscriptions {
planType
licenses
}
customerNetworks(first: 1) {
totalCount
}
adminUsers(first: 25) {
edges {
node {
email
}
}
}
}
}
}
}
}

customerNetworks(first: 1) { totalCount } returns the customer count without fetching every customer. Cache dashboard results for a few minutes. License counts do not change from second to second.

Monthly billing export

Use the paginated Customer Network query in Pagination to write one row per customer with its MSP Network, plan, and license count.

  • If subscription is null, the customer’s plan is still being set up. Mark it as pending and check again later.
  • To check the export, compare your totals per MSP Network with that MSP Network’s aggregatedSubscriptions. If they differ, a customer changed during the run. Run the export again.
  • Save each export with the date in its file name as a record of what you billed.

Change tracking

The API returns current state only. To track changes, save a snapshot of the billing export keyed by Customer Network id on each run, and compare it with the previous snapshot:

ChangeHow to detect it
New customerThe ID appears only in the new snapshot.
Removed customerThe ID appears only in the previous snapshot. The network was disabled or removed.
Plan changeSame ID, different planType.
License changeSame ID, different licenses.

PSA or CRM sync

Store each Customer Network id on the matching account in your PSA or CRM, for example in a custom field. On a schedule, run the billing export query and update the license quantity on each mapped account. Skip customers whose subscription is null until the plan is set.

Provisioning MSP Networks

To create MSP Portals from your own onboarding flow:

  • Convert the slug locally the same way Twingate does: remove every character that is not a letter or number, then convert to lowercase. Use the converted slug in every later step.
  • List your MSP Networks and check that no url starts with https://<converted slug>..
  • Ask a person to confirm, and show them the converted slug. The call creates a live network and sends an email.
  • Call mspNetworkCreate once with the converted slug.
  • Check ok. If it is false, show error to the user.
  • Check that the subdomain of entity.url matches the converted slug.
  • Save entity.id and entity.url against the account you are onboarding.
  • Tell the admin to expect an invitation email.

Monitoring

Every 15 to 60 minutes, send { partner { id } } from the same environment and with the same key as your scheduled jobs. Alert on a 401 response, repeated timeouts, or a null value for data.partner.

Errors and limits

Errors

SituationResponse
X-PARTNER-ID missing or invalid, or X-API-KEY missingHTTP 401: {"status": 401, "error": "Authentication credentials were not provided."}
Incorrect or revoked API keyHTTP 401: {"status": 401, "error": "Incorrect authentication credentials."}
GET requestHTTP 400. Use POST.
HTTP method other than GET, POST, or OPTIONSHTTP 405
Invalid query, or a query nested more than 20 levels deepHTTP 400 with an errors array. The query does not run.
Valid query that fails while it runsHTTP 200 with an errors array. data can contain partial results.
mspNetworkCreate validation failureHTTP 200 with ok: false and a message in error

An HTTP 200 response does not mean every field resolved. Check for errors on every response, even when data is present.

Limits

LimitValue
Networks per Partner1,000, counting MSP Networks and Customer Networks together
Query depth20 levels
Rate limitNone published. Keep polling modest and back off on 429 or 5xx responses.

Retries

  • Retry queries on 429, 502, 503, 504, and network errors with exponential backoff, for example after 1, 2, and 4 seconds.
  • Never retry mspNetworkCreate automatically. Check whether the network exists first.
  • Set a timeout on every request. 30 seconds is a reasonable default.

Security

  • Keep the API key on your server. Never include it in a browser app, mobile app, desktop tool, or a client-side environment variable such as NEXT_PUBLIC_*.
  • Store the key in a secrets manager, not in source code, committed configuration files, or shared documents.
  • Use a separate key for each system, for example one for billing and one for a dashboard, so you can replace one without affecting the other. To request additional keys, contact partnersupport@twingate.com.
  • To rotate a key, request a new key, deploy it everywhere, confirm it works, then ask Twingate to revoke the old key. Revocation is permanent.
  • If a key is exposed, contact partnersupport@twingate.com to revoke it immediately.
  • Require sign-in on any internal tool that can create MSP Networks.

Troubleshooting

SymptomLikely causeFix
401 “Authentication credentials were not provided.”X-PARTNER-ID or X-API-KEY is missing, or the Partner ID is invalidCheck that both headers are sent with these exact names and that the Partner ID was copied in full. Some proxies remove unrecognized headers.
401 “Incorrect authentication credentials.”The key is incorrect or revoked, or belongs to a different Partner IDCheck the key for extra spaces or line breaks. If it is correct, contact partnersupport@twingate.com to check whether it was revoked.
400 on a request that looks correctThe request uses GETUse POST for every request.
405 Method Not AllowedThe request uses a method other than GET, POST, or OPTIONSUse POST.
Browser request blocked by CORSThe API is called from a web pageCall the API from your server.
errors contains “Cannot query field”A field name is misspelled or does not exist on that typeCheck the schema. Use inline fragments with networks and network.
Only some customers are returnedThe integration stopped after the first pageLoop until hasNextPage is false.
A customer has no plansubscription is null because the plan is still being set upCheck again later.
An error on subscription or aggregatedSubscriptionsA Customer Network is on a plan the API cannot reportContact partnersupport@twingate.com. The flat Customer Network query still returns the other customers, but the MSP Network list can fail entirely.
Report totals do not match aggregatedSubscriptionsA customer changed during the runRun the report again.
Network slug is taken. after a timeoutThe first request succeededList your MSP Networks and look for the converted slug before you try again.
The new network’s URL does not match the slug you sentThe slug contained characters other than letters and numbers, or uppercase lettersTwingate converted the slug without returning an error. Use entity.url from the response as the network’s URL.
The new MSP admin did not receive the invitationThe email was filtered, or an alias address was usedCheck spam and mail filtering. Use a real mailbox that supports a social sign-in. If the email is still missing, contact partnersupport@twingate.com.

When you contact support, include the time and timezone, the operation name, the HTTP status, and the response body. Never include your API key.

→ Managed Service Providers

→ MSP Billing

Last updated