NewInstance Documentation

NewInstance

v1.0.0

NewInstance gives you one account, one organization and one API key for a set of products you can adopt one at a time.

Productslink

ProductWhat it does
BugWatchError monitoring, logs, traces, metrics and release health, with SDKs for servers, browsers and mobile apps.
Support TicketsA hosted support portal for your customers, and an API to create and read their tickets.
Secret ManagerEnd-to-end encrypted configuration your apps fetch at runtime.
ChatAn AI chatbot and live chat for your website, with native mobile SDKs.
Docs PortalBranded documentation for your own product, with an interactive API reference.
EHRElectronic health records for behavioral health and human services agencies.
AI Agent Access (MCP)An MCP endpoint that lets AI agents work with your data.

Base URLslink

ServiceURL
REST APIhttps://api.newinstance.cloud
MCP endpointhttps://service.newinstance.cloud

There is no separate sandbox host. Use a sk_test_ key on the same URLs and your calls only ever see test data.

Authenticationlink

Every request sends your API key in the x-api-key header. The Authentication page in this section shows the format, and Getting Started explains how to create a key and which scopes it needs. Browser and mobile SDKs never hold your secret key; their guides explain how they sign in.

Where to startlink

  1. Follow Getting Started to create an organization and an API key.
  2. Open the product you are integrating from the sidebar.
  3. When a call fails, Errors & Rate Limits explains every error and what to do about it.

Getting Started

1 endpoint

Verify API key

GET/api/auth/verify

Checks that your API key is valid and returns the organization it belongs to and its environment.

Run this first after generating a key to confirm it is active and to see whether it is a test or a live key. A malformed or revoked key is refused the same way as a missing one.

Authorization

x-api-keyAPI key · headerrequired

Responses

200Key valid
401Invalid or missing API key
403Key inactive
curl -X GET 'https://api.newinstance.cloud/api/auth/verify' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "success": true,
  "message": "API key is valid",
  "business": {
    "id": "org_abc123",
    "name": "Acme Corp",
    "slug": "acme-corp"
  },
  "key": {
    "id": "sk_test_abc123",
    "environment": "test"
  },
  "timestamp": "2026-06-26T10:00:00.000Z"
}

Browser Ingest

1 endpoint

Browser ingest - error event (session token)

POST/api/v1/bugwatch/ingest/browser

Sends a browser event authenticated with a session token minted by Server Ingest API → Mint browser session token (run that request first: it stores sessionToken). The payload contract is the same as server ingest. The browser's Origin header is checked against the project's allowed origins. When the token expires, the SDK mints a new one automatically.

Headers

x-bugwatch-session

Request body

application/json
{
  "level": 50,
  "time": {{nowMs}},
  "message": "Uncaught TypeError: Cannot read properties of undefined",
  "release": "web@3.1.0",
  "eventId": "evt_browser_abc123",
  "tags": { "surface": "spa", "route": "/checkout" },
  "user": { "id": "usr_456" },
  "contexts": { "cart": { "id": "cart_42", "items": 3 } },
  "breadcrumbs": [
    { "timestamp": {{nowMs}}, "category": "navigation", "message": "Navigated to /checkout", "level": 30 }
  ],
  "exception": {
    "type": "TypeError",
    "value": "Cannot read properties of undefined",
    "stacktrace": { "frames": [ { "filename": "checkout.js", "function": "calculateTotal", "lineno": 42, "colno": 15 } ] }
  }
}

Responses

202Error event ingested
401Invalid or expired session token
403Origin not allowed
curl -X POST 'https://api.newinstance.cloud/api/v1/bugwatch/ingest/browser' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "level": 50,
  "time": {{nowMs}},
  "message": "Uncaught TypeError: Cannot read properties of undefined",
  "release": "web@3.1.0",
  "eventId": "evt_browser_abc123",
  "tags": { "surface": "spa", "route": "/checkout" },
  "user": { "id": "usr_456" },
  "contexts": { "cart": { "id": "cart_42", "items": 3 } },
  "breadcrumbs": [
    { "timestamp": {{nowMs}}, "category": "navigation", "message": "Navigated to /checkout", "level": 30 }
  ],
  "exception": {
    "type": "TypeError",
    "value": "Cannot read properties of undefined",
    "stacktrace": { "frames": [ { "filename": "checkout.js", "function": "calculateTotal", "lineno": 42, "colno": 15 } ] }
  }
}'
Response
{
  "ingested": 1,
  "skipped": 0,
  "deduped": 0
}

Server Ingest API

2 endpoints

Mint browser session token

POST/api/v1/bugwatch/browser-session

Mints a short-lived browser session token. Call this from your backend with the secret key, then pass the token to your frontend. The secret never reaches the client.

Scope: ingest:write

Send the request with an empty body.

Authorization

x-api-keyAPI key · headerrequired

Responses

200Token minted
401Invalid or missing API key
403Missing permission scope
curl -X POST 'https://api.newinstance.cloud/api/v1/bugwatch/browser-session' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "token": "eyJhbGciOiJIUzI1NiJ9.example",
  "expiresAt": "2026-06-26T12:15:00.000Z"
}

Ingest – log event (JSON)

POST/api/v1/bugwatch/ingest

Ingests logs and error events from server-side or CI environments.

Scope: ingest:write

Send Content-Type: application/json with a single event or an array of events, or application/x-ndjson with one event per line. The body can be up to 2 MB.

Event fieldslink

FieldTypeNotes
levelnumber10/20/30/40/50/60
timenumberUnix ms timestamp
messagestringHuman-readable text
releasestringApp build version
environmentstringproduction / staging / development
eventIdstringOptional; enables deduplication within 10-min window
tagsobjectFlat key-value labels (≤50 keys)
userobject{id, email, username, ip}. Only these four keys are kept; others are silently dropped
traceIdstringHex distributed trace id (≤32 chars)
spanIdstringSpan id
exceptionobject{type, value, stacktrace: {frames}}
breadcrumbsarrayLeading events

Send an error eventlink

Set level to 50 and add exception, breadcrumbs and trace ids. The eventId enables deduplication: re-sending the same event within 10 minutes increments deduped instead of ingested, as the duplicate example response shows.

{
  "level": 50,
  "time": {{nowMs}},
  "message": "Uncaught TypeError: Cannot read properties of undefined",
  "release": "{{release}}",
  "environment": "{{environment}}",
  "eventId": "evt_unique_abc123",
  "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
  "spanId": "00f067aa0ba902b7",
  "contexts": { "cart": { "id": "cart_42", "items": 3 } },
  "tags": { "component": "checkout" },
  "user": { "id": "usr_456", "username": "alice" },
  "exception": {
    "type": "TypeError",
    "value": "Cannot read properties of undefined (reading 'price')",
    "stacktrace": {
      "frames": [
        { "filename": "checkout.js", "function": "calculateTotal", "lineno": 42, "colno": 15 },
        { "filename": "cart.js", "function": "applyDiscount", "lineno": 88, "colno": 7 }
      ]
    }
  },
  "breadcrumbs": [
    { "timestamp": {{nowMs}}, "category": "navigation", "message": "Navigated to /checkout", "level": "info" },
    { "timestamp": {{nowMs}}, "category": "ui.click", "message": "Clicked \"Apply coupon\"", "level": "info" }
  ]
}

Send a batch as NDJSONlink

Set Content-Type: application/x-ndjson and put one event on each line. Each line is an independent event. This is the preferred format for batching from server SDKs.

{"level":50,"time":{{nowMs}},"message":"Payment processing failed","release":"{{release}}","environment":"{{environment}}","tags":{"orderId":"ord_789"},"user":{"id":"usr_789"}}
{"level":30,"time":{{nowMs}},"message":"Retry attempt 1 of 3","release":"{{release}}","environment":"{{environment}}","tags":{"orderId":"ord_789"}}

Authorization

x-api-keyAPI key · headerrequired

Request body

application/json
{
  "level": 30,
  "time": {{nowMs}},
  "message": "User signed in",
  "release": "1.0.0",
  "environment": "production",
  "tags": { "region": "eu-west-1" },
  "user": { "id": "usr_123", "email": "alice@example.com" }
}

Responses

202Event ingested
401Invalid or missing API key
403Missing permission scope
429Rate limited
curl -X POST 'https://api.newinstance.cloud/api/v1/bugwatch/ingest' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "level": 30,
  "time": {{nowMs}},
  "message": "User signed in",
  "release": "1.0.0",
  "environment": "production",
  "tags": { "region": "eu-west-1" },
  "user": { "id": "usr_123", "email": "alice@example.com" }
}'
Response
{
  "ingested": 1,
  "skipped": 0,
  "deduped": 0
}

OpenTelemetry, Prometheus & Tracing

11 endpoints

OTLP/HTTP – ingest logs (JSON)

POST/v1/logs

Sends OpenTelemetry logs to BugWatch over OTLP/HTTP.

Scope: ingest:write

Send application/json, as in this example, or application/x-protobuf with a protobuf ExportLogsServiceRequest, which is what the OTel Collector sends. A fully accepted batch answers with an empty {} object.

Authorization

x-api-keyAPI key · headerrequired

Request body

application/json
{
  "resourceLogs": [
    {
      "resource": {
        "attributes": [
          {
            "key": "service.name",
            "value": {
              "stringValue": "my-service"
            }
          }
        ]
      },
      "scopeLogs": [
        {
          "logRecords": [
            {
              "timeUnixNano": "{{nowNano}}",
              "severityNumber": 9,
              "severityText": "INFO",
              "body": {
                "stringValue": "User signed in"
              },
              "attributes": [
                {
                  "key": "user.id",
                  "value": {
                    "stringValue": "usr_123"
                  }
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Responses

200All accepted
401Invalid or missing API key
429Rate limited
curl -X POST 'https://api.newinstance.cloud/v1/logs' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "resourceLogs": [
    {
      "resource": {
        "attributes": [
          {
            "key": "service.name",
            "value": {
              "stringValue": "my-service"
            }
          }
        ]
      },
      "scopeLogs": [
        {
          "logRecords": [
            {
              "timeUnixNano": "{{nowNano}}",
              "severityNumber": 9,
              "severityText": "INFO",
              "body": {
                "stringValue": "User signed in"
              },
              "attributes": [
                {
                  "key": "user.id",
                  "value": {
                    "stringValue": "usr_123"
                  }
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}'
Response
null

OTLP/HTTP – ingest traces (JSON)

POST/v1/traces

Sends OpenTelemetry traces to BugWatch over OTLP/HTTP.

Scope: ingest:write

Send application/json, as in this example, or application/x-protobuf with a protobuf ExportTraceServiceRequest, which is what the OTel Collector sends. A fully accepted batch answers with an empty {} object.

Authorization

x-api-keyAPI key · headerrequired

Request body

application/json
{
  "resourceSpans": [
    {
      "resource": {
        "attributes": [
          {
            "key": "service.name",
            "value": {
              "stringValue": "my-service"
            }
          }
        ]
      },
      "scopeSpans": [
        {
          "spans": [
            {
              "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
              "spanId": "00f067aa0ba902b7",
              "name": "HTTP GET /api/users",
              "kind": 2,
              "startTimeUnixNano": "{{nowNano}}",
              "endTimeUnixNano": "{{nowNano}}",
              "status": {
                "code": 1
              },
              "attributes": [
                {
                  "key": "http.method",
                  "value": {
                    "stringValue": "GET"
                  }
                },
                {
                  "key": "http.status_code",
                  "value": {
                    "intValue": 200
                  }
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Responses

200All accepted
401Invalid or missing API key
429Rate limited
curl -X POST 'https://api.newinstance.cloud/v1/traces' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "resourceSpans": [
    {
      "resource": {
        "attributes": [
          {
            "key": "service.name",
            "value": {
              "stringValue": "my-service"
            }
          }
        ]
      },
      "scopeSpans": [
        {
          "spans": [
            {
              "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
              "spanId": "00f067aa0ba902b7",
              "name": "HTTP GET /api/users",
              "kind": 2,
              "startTimeUnixNano": "{{nowNano}}",
              "endTimeUnixNano": "{{nowNano}}",
              "status": {
                "code": 1
              },
              "attributes": [
                {
                  "key": "http.method",
                  "value": {
                    "stringValue": "GET"
                  }
                },
                {
                  "key": "http.status_code",
                  "value": {
                    "intValue": 200
                  }
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}'
Response
null

OTLP/HTTP – ingest metrics (JSON)

POST/v1/metrics

Sends OpenTelemetry metrics to BugWatch over OTLP/HTTP.

Scope: ingest:write

Send application/json, as in this example, or application/x-protobuf with a protobuf ExportMetricsServiceRequest, which is what the OTel Collector sends. A fully accepted batch answers with an empty {} object.

Authorization

x-api-keyAPI key · headerrequired

Request body

application/json
{
  "resourceMetrics": [
    {
      "resource": {
        "attributes": [
          {
            "key": "service.name",
            "value": {
              "stringValue": "my-service"
            }
          }
        ]
      },
      "scopeMetrics": [
        {
          "metrics": [
            {
              "name": "http.server.request.duration",
              "unit": "ms",
              "gauge": {
                "dataPoints": [
                  {
                    "timeUnixNano": "{{nowNano}}",
                    "asDouble": 123.4,
                    "attributes": [
                      {
                        "key": "http.method",
                        "value": {
                          "stringValue": "GET"
                        }
                      }
                    ]
                  }
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}

Responses

200All accepted
401Invalid or missing API key
429Rate limited
curl -X POST 'https://api.newinstance.cloud/v1/metrics' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "resourceMetrics": [
    {
      "resource": {
        "attributes": [
          {
            "key": "service.name",
            "value": {
              "stringValue": "my-service"
            }
          }
        ]
      },
      "scopeMetrics": [
        {
          "metrics": [
            {
              "name": "http.server.request.duration",
              "unit": "ms",
              "gauge": {
                "dataPoints": [
                  {
                    "timeUnixNano": "{{nowNano}}",
                    "asDouble": 123.4,
                    "attributes": [
                      {
                        "key": "http.method",
                        "value": {
                          "stringValue": "GET"
                        }
                      }
                    ]
                  }
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}'
Response
null

Prometheus remote-write (DOCUMENTATION ONLY)

POST/api/v1/prom/write

Receives Prometheus remote-write samples.

Scope: ingest:write

To use Prometheus remote-write with BugWatch:

  1. Deploy the bugwatch-otel-collector (or configure Prometheus remote_write).
  2. Set the remote-write URL to {{baseUrl}}/api/v1/prom/write.
  3. Add the header x-api-key: sk_live_KEYID:secret.

Authorization

x-api-keyAPI key · headerrequired

Headers

X-Prometheus-Remote-Write-Version

Request body

text/plain
<<< BINARY SNAPPY-COMPRESSED PROTOBUF - sent by Prometheus/collector, not a manual JSON request >>>

Responses

204Samples accepted
400Malformed protobuf
401Invalid or missing API key
429Rate limited
curl -X POST 'https://api.newinstance.cloud/api/v1/prom/write' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: text/plain' \
  --data-raw '<<< BINARY SNAPPY-COMPRESSED PROTOBUF - sent by Prometheus/collector, not a manual JSON request >>>'
Response
null

Prometheus query (instant)

GET/api/v1/prom/query

Returns the latest value of each matching metric series at one point in time, using a PromQL-compatible selector.

Scope: ingest:write (the key you write with is accepted for queries)

The same query also works as a POST with form-encoded parameters, which is what Grafana's Prometheus data source sends.

Authorization

x-api-keyAPI key · headerrequired

Parameters

queryquery · stringdefault:

Required. Metric name or label selector, such as http_requests_total or {service="api-gateway"}.

timequery · stringdefault:

Optional. Evaluation time in Unix seconds (decimals allowed). Defaults to now.

Responses

200Instant vector
401Invalid or missing API key
curl -X GET 'https://api.newinstance.cloud/api/v1/prom/query?query=http_requests_total&time=1767225600' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "status": "success",
  "data": {
    "resultType": "vector",
    "result": [
      {
        "metric": {
          "__name__": "http_requests_total",
          "service": "api"
        },
        "value": [
          1767225600,
          "42"
        ]
      }
    ]
  }
}

Prometheus query_range (over time)

GET/api/v1/prom/query_range

Returns every stored point of each matching metric series over a time range.

Scope: ingest:write

The same query also works as a POST with form-encoded parameters, which is what Grafana's Prometheus data source sends.

Authorization

x-api-keyAPI key · headerrequired

Parameters

queryquery · stringdefault:

Required. Metric name or label selector, such as http_requests_total or {service="api-gateway"}.

startquery · stringdefault:

Range start in Unix seconds (decimals allowed). Defaults to 0, the oldest stored point.

endquery · stringdefault:

Range end in Unix seconds (decimals allowed). Defaults to now.

Responses

200Matrix result
401Invalid or missing API key
curl -X GET 'https://api.newinstance.cloud/api/v1/prom/query_range?query=http_requests_total&start=1767222000&end=1767225600' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "status": "success",
  "data": {
    "resultType": "matrix",
    "result": [
      {
        "metric": {
          "__name__": "http_requests_total"
        },
        "values": [
          [
            1767222000,
            "38"
          ],
          [
            1767225600,
            "42"
          ]
        ]
      }
    ]
  }
}

Prometheus label __name__ values (list metric names)

GET/api/v1/prom/label/__name__/values

Lists every distinct metric name stored for the project and environment your key is scoped to.

Scope: ingest:write

Authorization

x-api-keyAPI key · headerrequired

Responses

200Metric names
401Invalid or missing API key
curl -X GET 'https://api.newinstance.cloud/api/v1/prom/label/__name__/values' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "status": "success",
  "data": [
    "http_requests_total",
    "rum.lcp",
    "rum.cls",
    "rum.inp"
  ]
}

Jaeger – list service names

GET/jaeger/api/services

Lists the distinct service names that have sent traces to this BugWatch project.

Scope: ingest:write (the same API key)

Authorization

x-api-keyAPI key · headerrequired

Responses

200Services
401Invalid or missing API key
curl -X GET 'https://api.newinstance.cloud/jaeger/api/services' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "data": [
    "api-gateway",
    "payment-service",
    "user-service"
  ],
  "total": 3,
  "limit": 3,
  "offset": 0,
  "errors": null
}

Jaeger – list traces

GET/jaeger/api/traces

Lists recent distributed traces for the project and environment your key is scoped to, newest first, each with its spans in the Jaeger format.

Scope: ingest:write

Authorization

x-api-keyAPI key · headerrequired

Parameters

limitquery · stringdefault:

Optional. Maximum number of traces, up to 100. Defaults to 20.

lookbackquery · stringdefault:

Optional. How far back to look: 1h, 24h, 7d or 30d. Defaults to 1h; any other value is treated as 1h.

Responses

200Traces list
401Invalid or missing API key
curl -X GET 'https://api.newinstance.cloud/jaeger/api/traces?limit=20&lookback=1h' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "data": [],
  "total": 0,
  "limit": 20,
  "offset": 0,
  "errors": null
}

Jaeger – get one trace

GET/jaeger/api/traces/{traceId}

Returns every span of one trace in the Jaeger format.

Scope: ingest:write

traceId in the path is the 32-character hex trace identifier, as sent in traceparent or returned by Jaeger – list traces. A trace id with no stored spans answers 200 with an empty data list, not 404.

Authorization

x-api-keyAPI key · headerrequired

Parameters

traceIdpath · stringrequireddefault:

Responses

200Trace
401Invalid or missing API key
curl -X GET 'https://api.newinstance.cloud/jaeger/api/traces/{traceId}' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "data": [
    {
      "traceID": "4bf92f3577b34da6a3ce929d0e0e4736",
      "spans": []
    }
  ],
  "total": 1,
  "limit": 1,
  "offset": 0,
  "errors": null
}

RUM – ingest web-vitals

POST/api/v1/rum

Ingests Real User Monitoring (RUM) web-vitals metrics (LCP, CLS, INP, FID, TTFB).

Authenticate with x-bugwatch-session from a browser, or with x-api-key from a server or while testing.

Scope (for x-api-key): ingest:write

Request body: three shapes are accepted.

  1. Wrapped array:
{ "vitals": [{ "name": "LCP", "value": 1250.5, "url": "https://example.com/", "rating": "good" }] }
  1. Bare array:
[{ "name": "CLS", "value": 0.05, "rating": "good" }, { "name": "INP", "value": 180, "rating": "needs-improvement" }]
  1. Single vital object:
{ "name": "TTFB", "value": 320, "url": "https://example.com/checkout", "rating": "needs-improvement" }

Vital field schema:

FieldTypeNotes
namestring (required)LCP, CLS, INP, FID, TTFB
valuenumber (required)Metric value in ms (or unitless for CLS)
urlstring (optional)Page URL
ratingstring (optional)good, needs-improvement or poor
idstring (optional)Client-side unique id

Vitals are stored as rum.<name_lower> metric points (for example rum.lcp, rum.cls). A browser call from an origin outside the project's allowed origins is refused with 401, the same as a bad session token.

Authorization

x-api-keyAPI key · headerrequired

Request body

application/json
{
  "vitals": [
    {
      "name": "LCP",
      "value": 1250.5,
      "url": "https://example.com/",
      "rating": "good"
    },
    {
      "name": "CLS",
      "value": 0.05,
      "url": "https://example.com/",
      "rating": "good"
    },
    {
      "name": "INP",
      "value": 180,
      "url": "https://example.com/",
      "rating": "needs-improvement"
    }
  ]
}

Responses

202Vitals ingested
400Malformed JSON
401Invalid or missing API key
429Rate limited
curl -X POST 'https://api.newinstance.cloud/api/v1/rum' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "vitals": [
    {
      "name": "LCP",
      "value": 1250.5,
      "url": "https://example.com/",
      "rating": "good"
    },
    {
      "name": "CLS",
      "value": 0.05,
      "url": "https://example.com/",
      "rating": "good"
    },
    {
      "name": "INP",
      "value": 180,
      "url": "https://example.com/",
      "rating": "needs-improvement"
    }
  ]
}'
Response
{
  "ingested": 3
}

Source Maps & Symbols

7 endpoints

Artifact upload · step 1 – presign

POST/api/v1/bugwatch/artifacts/presign

Requests a presigned URL for a text symbolication artifact. This is step 1 of 2.

Scope: symbols:upload · Auth: project secret key (keyId:secret)

artifactTypeFilePlatform
r8 / proguardmapping.txtandroid
sourcemap.mapios · react-native · android
dart-symbols--split-debug-info outputflutter
dsymApple dSYM referenced as an artifactios

The file never passes through this API. You presign, PUT the bytes straight to storage, then confirm. That is why uploads are not bound by request-size limits; the previous raw-body routes were rejected by the edge proxy for any real symbol file.

1. POST  …/presign                       → { uploadId, uploadUrl, expiresAt }
2. PUT   <uploadUrl>                      → the raw bytes, streamed to storage
3. POST  …/uploads/{uploadId}/complete    → verified, then queued

The API key goes on steps 1 and 3 only, never to the storage host, which is authorized by the presigned URL's own signature.

Uploads are idempotent per (release, platform, artifactType): completing a new upload replaces the previous artifact and deletes the superseded file.

The release you declare must match the release your SDK reports at runtime, or the artifact exists but nothing matches it and stacks stay unreadable.

Most integrators should use the CLI, which performs all three steps, streams the file (flat memory regardless of size) and retries transient failures:

# Binary debug-symbol archives - Apple dSYM, Android native .so
npx @newinstance/bugwatch-cli symbols upload MyApp.xcarchive \
  --release "1.4.2" --build-number "318"

# Text artifacts - R8/ProGuard mapping, source map, Dart symbols
npx @newinstance/bugwatch-cli artifacts upload mapping.txt \
  --release "1.4.2" --platform android --type r8

The two commands are not interchangeable: symbols upload accepts binary archives only and validates magic bytes, so a mapping.txt or .map is rejected.

Authorization

x-api-keyAPI key · headerrequired

Request body

application/json
{
  "release": "1.0.0",
  "platform": "android",
  "artifactType": "r8",
  "size": 128394,
  "sha256": "3b8f2a91c0de4b7a8f13d6c25e09a4b1f87c30d2416e5a9b8c7d0e1f2a3b4c1d",
  "originalName": "mapping.txt"
}

Responses

201Presigned
400Validation error
401Invalid or missing API key
403Missing symbols:upload
curl -X POST 'https://api.newinstance.cloud/api/v1/bugwatch/artifacts/presign' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "release": "1.0.0",
  "platform": "android",
  "artifactType": "r8",
  "size": 128394,
  "sha256": "3b8f2a91c0de4b7a8f13d6c25e09a4b1f87c30d2416e5a9b8c7d0e1f2a3b4c1d",
  "originalName": "mapping.txt"
}'
Response
{
  "uploadId": "665f1f77bcf86cd799439021",
  "uploadUrl": "https://storage.newinstance.cloud/presigned/665f1f77bcf86cd799439021?sig=…",
  "expiresAt": "2026-08-05T12:15:00.000Z"
}

Artifact upload · step 2 – complete

POST/api/v1/bugwatch/artifacts/uploads/{uploadId}/complete

Confirms the artifact landed in storage and makes it the active artifact for its (release, platform, artifactType). This is step 2 of 2.

Scope: symbols:upload

uploadId in the path is the id returned by step 1, the presign request.

What is verified

  • complete confirms the object actually landed in storage and that its size matches what was declared at presign.
  • The pending row is looked up scoped to the calling project, so one project can never finalize another's upload (a miss returns 404).
  • Only an AWAITING_UPLOAD row can be completed, so a completion cannot be replayed to re-queue processing. Completing it again answers 200 with alreadyCompleted: true.
  • For debug symbols the worker re-hashes the real bytes before indexing and rejects a mismatch (CHECKSUM_MISMATCH). A declared checksum is never trusted on its own.

Authorization

x-api-keyAPI key · headerrequired

Parameters

uploadIdpath · stringrequireddefault:

Responses

200Upload already completed
201Stored and active
400Size mismatch
401Invalid or missing API key
404Upload not found
409File never uploaded
curl -X POST 'https://api.newinstance.cloud/api/v1/bugwatch/artifacts/uploads/{uploadId}/complete' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "fileId": "f_9a2b1c3d4e5f",
  "sha256": "3b8f2a91c0de4b7a8f13d6c25e09a4b1f87c30d2416e5a9b8c7d0e1f2a3b4c1d",
  "alreadyCompleted": true
}

Debug symbols · step 1 – presign

POST/api/v1/bugwatch/debug-symbols/presign

Requests a presigned URL for a binary debug-symbol archive. This is step 1 of 2.

Scope: symbols:upload · Auth: project secret key (keyId:secret)

PlatformAccepted
ios · macos · tvos · watchos · visionos · catalyst.zip of .dSYM bundles, .xcarchive dSYMs, or a raw Mach-O
androidRaw ELF .so / .debug

The file never passes through this API. You presign, PUT the bytes straight to storage, then confirm. That is why uploads are not bound by request-size limits; the previous raw-body routes were rejected by the edge proxy for any real symbol file.

1. POST  …/presign                       → { uploadId, uploadUrl, expiresAt }
2. PUT   <uploadUrl>                      → the raw bytes, streamed to storage
3. POST  …/uploads/{uploadId}/complete    → verified, then queued

The API key goes on steps 1 and 3 only, never to the storage host, which is authorized by the presigned URL's own signature.

Matching is by debug UUID / build-id, the identifier embedded in the binary, not by release name. That is why an upload from any build machine resolves crashes from any device running that exact binary, and why release here is metadata for search rather than the match key. (Source maps and mappings are the opposite: those match on release + platform.)

Declaring a sha256 that already exists for this project short-circuits as a duplicate and returns no uploadUrl, so the transfer is skipped entirely.

Most integrators should use the CLI, which performs all three steps, streams the file (flat memory regardless of size) and retries transient failures:

# Binary debug-symbol archives - Apple dSYM, Android native .so
npx @newinstance/bugwatch-cli symbols upload MyApp.xcarchive \
  --release "1.4.2" --build-number "318"

# Text artifacts - R8/ProGuard mapping, source map, Dart symbols
npx @newinstance/bugwatch-cli artifacts upload mapping.txt \
  --release "1.4.2" --platform android --type r8

The two commands are not interchangeable: symbols upload accepts binary archives only and validates magic bytes, so a mapping.txt or .map is rejected.

Authorization

x-api-keyAPI key · headerrequired

Request body

application/json
{
  "platform": "ios",
  "release": "1.0.0",
  "buildNumber": "318",
  "size": 48210944,
  "sha256": "9c1e5b7a3f28d4c6b0a1e9f2d5c8b7a64310f2e7d9c1b5a8e3f60412789bcf02",
  "originalName": "MyApp.app.dSYM.zip"
}

Responses

200Archive already uploaded, transfer skipped
201Presigned
400Validation error
401Invalid or missing API key
403Missing symbols:upload
curl -X POST 'https://api.newinstance.cloud/api/v1/bugwatch/debug-symbols/presign' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "platform": "ios",
  "release": "1.0.0",
  "buildNumber": "318",
  "size": 48210944,
  "sha256": "9c1e5b7a3f28d4c6b0a1e9f2d5c8b7a64310f2e7d9c1b5a8e3f60412789bcf02",
  "originalName": "MyApp.app.dSYM.zip"
}'
Response
{
  "uploadId": "665f1f77bcf86cd799439011",
  "status": "AVAILABLE",
  "duplicate": true
}

Debug symbols · step 2 – complete

POST/api/v1/bugwatch/debug-symbols/uploads/{uploadId}/complete

Confirms the archive landed in storage and queues it for indexing. This is step 2 of 2.

Scope: symbols:upload

uploadId in the path is the id returned by step 1, the presign request. After this call, poll Get upload status for indexing progress.

What is verified

  • complete confirms the object actually landed in storage and that its size matches what was declared at presign.
  • The pending row is looked up scoped to the calling project, so one project can never finalize another's upload (a miss returns 404).
  • Only an AWAITING_UPLOAD row can be completed, so a completion cannot be replayed to re-queue processing. Completing it again answers 200 with alreadyCompleted: true.
  • For debug symbols the worker re-hashes the real bytes before indexing and rejects a mismatch (CHECKSUM_MISMATCH). A declared checksum is never trusted on its own.

Archive-type validation happens in the worker, not here, because this API never sees the bytes. An archive that is not a zip / Mach-O / ELF lands as INVALID with UNRECOGNIZED_ARCHIVE; an ELF declared under an Apple platform fails with ARCHIVE_PLATFORM_MISMATCH.

Authorization

x-api-keyAPI key · headerrequired

Parameters

uploadIdpath · stringrequireddefault:

Responses

200Queued for indexing
400Size mismatch
401Invalid or missing API key
404Upload not found
409File never uploaded
curl -X POST 'https://api.newinstance.cloud/api/v1/bugwatch/debug-symbols/uploads/{uploadId}/complete' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "uploadId": "665f1f77bcf86cd799439031",
  "status": "QUEUED"
}

List debug symbol uploads

GET/api/v1/bugwatch/debug-symbols/uploads

Lists this project's recent debug symbol uploads, newest first.

Scope: symbols:read

Authorization

x-api-keyAPI key · headerrequired

Parameters

limitquery · stringdefault:

Optional. Number of uploads to return, 1 to 200. Defaults to 50.

Responses

200Uploads list
401Invalid or missing API key
403Missing symbols:read
curl -X GET 'https://api.newinstance.cloud/api/v1/bugwatch/debug-symbols/uploads?limit=20' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "uploads": [
    {
      "uploadId": "507f1f77bcf86cd799439011",
      "status": "AVAILABLE",
      "platform": "ios",
      "release": "2.1.0",
      "buildNumber": "1042",
      "originalFilename": "MyApp.app.dSYM.zip",
      "uploadedSize": 4823012,
      "discoveredUuids": 3,
      "validObjects": 3,
      "invalidObjects": 0,
      "createdAt": "2026-06-26T10:00:00.000Z",
      "completedAt": "2026-06-26T10:00:15.000Z"
    }
  ]
}

Get upload status

GET/api/v1/bugwatch/debug-symbols/uploads/{uploadId}

Returns the processing status and indexing summary of one debug symbol upload.

Scope: symbols:read

uploadId in the path comes from the presign response.

Status flow: QUEUED, then EXTRACTING, then one of AVAILABLE, PARTIALLY_AVAILABLE (some objects were invalid), INVALID or FAILED.

Poll this until status is AVAILABLE or PARTIALLY_AVAILABLE before relying on symbolication.

Authorization

x-api-keyAPI key · headerrequired

Parameters

uploadIdpath · stringrequireddefault:

Responses

200Upload detail
400Upload id is not valid
401Invalid or missing API key
404Upload not found
curl -X GET 'https://api.newinstance.cloud/api/v1/bugwatch/debug-symbols/uploads/{uploadId}' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "uploadId": "507f1f77bcf86cd799439011",
  "status": "AVAILABLE",
  "platform": "ios",
  "release": "2.1.0",
  "buildNumber": "1042",
  "discoveredDsyms": 2,
  "discoveredUuids": 3,
  "validObjects": 3,
  "invalidObjects": 0,
  "failureCode": null,
  "failureMessage": null,
  "createdAt": "2026-06-26T10:00:00.000Z",
  "completedAt": "2026-06-26T10:00:15.000Z"
}

Reprocess – re-symbolicate waiting crashes

POST/api/v1/bugwatch/debug-symbols/uploads/{uploadId}/reprocess

Re-symbolicates crashes that arrived before this upload's symbols did.

Scope: symbols:reprocess

uploadId in the path is the debug symbol upload the waiting crashes need. Crashes still waiting for its symbols, or only partly symbolicated, are queued again and processed with them. Send an empty body.

Authorization

x-api-keyAPI key · headerrequired

Parameters

uploadIdpath · stringrequireddefault:

Responses

200Crashes queued for re-symbolication
400Upload id is not valid
401Invalid or missing API key
403Missing symbols:reprocess
404Upload not found
curl -X POST 'https://api.newinstance.cloud/api/v1/bugwatch/debug-symbols/uploads/{uploadId}/reprocess' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "requeued": 12
}

Deployments (CI/CD)

3 endpoints

Start deployment

POST/api/v1/bugwatch/deploy

Creates a deployment record. Call it at the start of your CI deploy job.

Scope: deploy:write

Every field is optional, and an empty body is valid.

FieldTypeRequiredDescription
releasestringNoThe release this deploy ships, up to 200 characters. Match what your SDK reports at runtime so errors correlate.
metaobjectNoString map with commit, repo, branch and triggeredBy, each up to 300 characters.

Keep the returned id: every later call needs it. The status starts as in_progress. A 404 means the project bound to this key no longer exists.

Authorization

x-api-keyAPI key · headerrequired

Request body

application/json
{
  "release": "1.0.0",
  "meta": {
    "commit": "9f2c1ab",
    "repo": "acme/checkout",
    "branch": "main",
    "triggeredBy": "gitlab-ci"
  }
}

Responses

201Deployment created
401Invalid or missing API key
403Missing deploy:write
404Project not found
curl -X POST 'https://api.newinstance.cloud/api/v1/bugwatch/deploy' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "release": "1.0.0",
  "meta": {
    "commit": "9f2c1ab",
    "repo": "acme/checkout",
    "branch": "main",
    "triggeredBy": "gitlab-ci"
  }
}'
Response
{
  "id": "dep_64fa3c9e2b7d41c8a9f01234"
}

Report stage · finish · fail

PATCH/api/v1/bugwatch/deploy/{deployId}

Updates a deployment with a stage transition, a finish or an explicit failure. The body carries one of stage, finish or fail.

Scope: deploy:write

deployId in the path is the id returned by Start deployment. Only a key for the same project can update it; any other key gets 404.

1. Stage transition

{ "stage": { "key": "migrate", "name": "Run DB migrations", "status": "running" } }

status is running, succeeded or failed; exitCode and errorSummary are optional. Repeat per stage. A stage key is upserted, so re-sending updates it.

2. Finish

{ "finish": true }

Final status is derived from stages: any failed stage ⇒ failed, else succeeded. A failed finish opens a DeploymentFailure issue in the project.

3. Fail explicitly

{ "fail": { "summary": "migrate exited 1", "stageKey": "migrate" } }

Once the status is no longer in_progress, further PATCHes are no-ops that echo the final status, so they are safe to retry.

StatusWhen it happens
400A per-deployment stage or log limit is exceeded

Authorization

x-api-keyAPI key · headerrequired

Parameters

deployIdpath · stringrequireddefault:

Request body

application/json
{
  "stage": {
    "key": "migrate",
    "name": "Run DB migrations",
    "status": "succeeded",
    "exitCode": 0
  }
}

Responses

200Stage recorded
401Invalid or missing API key
403Missing deploy:write
404Deployment not found
curl -X PATCH 'https://api.newinstance.cloud/api/v1/bugwatch/deploy/{deployId}' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "stage": {
    "key": "migrate",
    "name": "Run DB migrations",
    "status": "succeeded",
    "exitCode": 0
  }
}'
Response
{
  "id": "dep_64fa3c9e2b7d41c8a9f01234",
  "status": "in_progress"
}

Append stage logs (NDJSON)

POST/api/v1/bugwatch/deploy/{deployId}/logs

Streams a batch of log lines for one stage. Call it repeatedly while the stage runs; the CLI's deploy run does this for you from the wrapped command's stdout/stderr.

Scope: deploy:write · Max body: 2 MB per batch

deployId in the path is the id returned by Start deployment.

Body: NDJSON (one JSON object per line) or a JSON array of { level, time, message }:

  • level: Pino-style number, 10 trace · 20 debug · 30 info · 40 warn · 50 error · 60 fatal
  • time: epoch milliseconds
  • message: the log line

Deploy tags (deployId, stage) are injected server-side, so do not add them yourself. Lines land in the project's normal log search.

truncated: true means the per-deployment log cap was reached; further batches are dropped, while finish and fail still work.

Authorization

x-api-keyAPI key · headerrequired

Parameters

stagequery · stringdefault:

Required. Key of the stage these log lines belong to, up to 100 characters, such as migrate.

deployIdpath · stringrequireddefault:

Request body

application/json
{"level":30,"time":{{nowMs}},"message":"Applying migration 0042_add_billing_index"}
{"level":30,"time":{{nowMs}},"message":"Migration complete in 1.2s"}

Responses

202Batch accepted
401Invalid or missing API key
403Missing deploy:write
404Deployment not found
curl -X POST 'https://api.newinstance.cloud/api/v1/bugwatch/deploy/{deployId}/logs?stage=migrate' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"level":30,"time":{{nowMs}},"message":"Applying migration 0042_add_billing_index"}
{"level":30,"time":{{nowMs}},"message":"Migration complete in 1.2s"}'
Response
{
  "ingested": 2,
  "skipped": 0,
  "deduped": 0,
  "truncated": false
}

Support Tickets

5 endpoints

List customer tickets

GET/api/v1/support-tickets/tickets

Lists one customer's tickets, page by page. The customer is identified by email.

Scope: full-access, ticket-management or read-only

Authorization

x-api-keyAPI key · headerrequired

Parameters

customerEmailquery · stringdefault:

Required. Email of the customer whose tickets to list.

pagequery · stringdefault:

Optional. Page number, from 1. Defaults to 1.

limitquery · stringdefault:

Optional. Tickets per page, 1 to 100. Defaults to 20.

Responses

200Tickets list
400Missing customerEmail
401Invalid or missing API key
403Missing permission scope
curl -X GET 'https://api.newinstance.cloud/api/v1/support-tickets/tickets?customerEmail=alice%40example.com&page=1&limit=20' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "tickets": [
    {
      "ticketId": "tkt_abc123",
      "title": "Unable to export invoice PDF",
      "status": "OPEN",
      "priority": "HIGH",
      "createdAt": "2026-06-26T10:00:00.000Z"
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 20
}

Create ticket

POST/api/v1/support-tickets/tickets

Creates a support ticket on behalf of a customer, and creates the customer record if it does not exist yet.

Scope: full-access or ticket-management

Request body

FieldTypeNotes
titlestring (required, max 200)Ticket title
descriptionstring (required, max 5000)Full description
customerNamestring (required, max 100)Customer full name
customerEmailstring (required, email)Customer email
prioritystring (optional)LOW, NORMAL, HIGH or URGENT
categorystring (optional, max 100)Ticket category

The returned ticketId is what the detail and comment endpoints take.

Authorization

x-api-keyAPI key · headerrequired

Request body

application/json
{
  "title": "Unable to export invoice PDF",
  "description": "When I click \"Export as PDF\" on any invoice page, I get a blank file. Tested on Chrome 125 and Firefox 128. The file is 0 bytes. My account ID is ACC-4471.",
  "customerName": "Alice Smith",
  "customerEmail": "alice@example.com",
  "priority": "HIGH",
  "category": "Billing"
}

Responses

201Ticket created
400Validation error
401Invalid or missing API key
403Missing permission scope
curl -X POST 'https://api.newinstance.cloud/api/v1/support-tickets/tickets' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "title": "Unable to export invoice PDF",
  "description": "When I click \"Export as PDF\" on any invoice page, I get a blank file. Tested on Chrome 125 and Firefox 128. The file is 0 bytes. My account ID is ACC-4471.",
  "customerName": "Alice Smith",
  "customerEmail": "alice@example.com",
  "priority": "HIGH",
  "category": "Billing"
}'
Response
{
  "ticketId": "tkt_abc123",
  "status": "OPEN",
  "createdAt": "2026-06-26T10:00:00.000Z"
}

Get ticket detail

GET/api/v1/support-tickets/tickets/{ticketId}

Returns one ticket with its public, customer-facing comments.

Scope: full-access, ticket-management or read-only

ticketId in the path is the ticketId returned by Create ticket; Postman fills it in after that request runs. customerEmail must belong to the customer who owns the ticket: any other email answers 404, so one customer can never read another's ticket.

Authorization

x-api-keyAPI key · headerrequired

Parameters

customerEmailquery · stringdefault:

Required. Email of the customer who owns the ticket, used as the ownership check.

ticketIdpath · stringrequireddefault:

Responses

200Ticket detail
400Missing customerEmail
401Invalid or missing API key
403Missing permission scope
404Ticket not found
curl -X GET 'https://api.newinstance.cloud/api/v1/support-tickets/tickets/{ticketId}?customerEmail=alice%40example.com' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "ticketId": "tkt_abc123",
  "title": "Unable to export invoice PDF",
  "status": "OPEN",
  "priority": "HIGH",
  "category": "Billing",
  "description": "When I click \"Export as PDF\"...",
  "createdAt": "2026-06-26T10:00:00.000Z",
  "comments": []
}

Add customer comment

POST/api/v1/support-tickets/tickets/{ticketId}/comments

Adds a public comment from the customer to a ticket.

Scope: full-access or ticket-management

ticketId in the path is the ticketId returned by Create ticket; Postman fills it in after that request runs.

A comment on a RESOLVED ticket reopens it to OPEN. A CLOSED ticket takes no more comments.

Request body

FieldTypeNotes
customerEmailstring (required, email)Ownership verification
contentstring (required, max 5000)Comment text
attachmentsarray (optional, max 5)Each item: URI string or {url, name} object. Host the file at a URL you control (your storage or CDN); the API stores the reference, it does not accept file uploads

Authorization

x-api-keyAPI key · headerrequired

Parameters

ticketIdpath · stringrequireddefault:

Request body

application/json
{
  "customerEmail": "alice@example.com",
  "content": "I tried on Edge as well and the PDF is still blank. I noticed it only happens for invoices older than 90 days. Recent invoices export fine.",
  "attachments": [
    {
      "url": "https://storage.example.com/screenshots/blank-pdf.png",
      "name": "blank-pdf.png"
    }
  ]
}

Responses

201Comment added
400Ticket is closed
401Invalid or missing API key
403Missing permission scope
404Ticket not found
curl -X POST 'https://api.newinstance.cloud/api/v1/support-tickets/tickets/{ticketId}/comments' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "customerEmail": "alice@example.com",
  "content": "I tried on Edge as well and the PDF is still blank. I noticed it only happens for invoices older than 90 days. Recent invoices export fine.",
  "attachments": [
    {
      "url": "https://storage.example.com/screenshots/blank-pdf.png",
      "name": "blank-pdf.png"
    }
  ]
}'
Response
{
  "commentId": "cmt_xyz789",
  "createdAt": "2026-06-26T10:05:00.000Z"
}

Secret Manager

2 endpoints

Get encrypted master key

GET/api/v1/secret-manager/apps/{appId}/master-key

Returns an app's Master Encryption Key (MEK), still encrypted under your App Secret.

Scope: full-access or secret-read

appId in the path is the App ID from the Secret Manager dashboard (set it as {{appId}} in your environment).

Unwrap encryptedMasterKey locally with your App Secret, using masterKeyIV and masterKeyTag (AES-256-GCM). The nismsdk package does this for you with decryptMasterKey. Each call counts toward your Secret Manager request allowance, and 429 means the allowance is used up under a hard limit.

Authorization

x-api-keyAPI key · headerrequired

Parameters

appIdpath · stringrequireddefault:

Responses

200Encrypted MEK
400App secret not set up yet
401Invalid or missing API key
403Missing permission scope
404App not found
429Rate limited
curl -X GET 'https://api.newinstance.cloud/api/v1/secret-manager/apps/{appId}/master-key' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "success": true,
  "data": {
    "appId": "66f1a2b3c4d5e6f7a8b9c0a1",
    "appName": "checkout-api",
    "encryptedMasterKey": "k3J9vQx0bW1lc3NhZ2UtY2lwaGVydGV4dA==",
    "masterKeyIV": "q8Zx4Lm2Tn9Pw1Rs",
    "masterKeyTag": "Vb7nC2xQ9mL4kP0sR6tY1w=="
  }
}

Get encrypted variables

GET/api/v1/secret-manager/apps/{appId}/variables

Returns an app's environment variables for one environment, each value still encrypted under the app's MEK.

Scope: full-access or secret-read

appId in the path is the App ID from the Secret Manager dashboard.

Decrypt each value locally with the MEK from Get encrypted master key, using the variable's iv and tag (AES-256-GCM). The nismsdk package does this for you with decryptWithMasterKey. An environment with no variables answers 200 with an empty variables list.

The published CLI/SDK nismsdk (Node 18+, binaries nism and nismsdk) wraps the fetch + local-decrypt flow; plaintext never transits the platform.

npm i -g nismsdk        # or: npx nismsdk <command>

nism setup              # store appId / orgId / apiKey in ~/.nism/config.json
nism verify             # fetch, decrypt and confirm values are readable
nism load --environment production --create-env .env
eval "$(nism env --appSecret "$NISM_APP_SECRET" --format shell)" && node server.js

Commands: load (the default when no subcommand is given; flags include --environment, --create-env <file>, --env-set-name (default default) and --system-level), setup, config [--show-secrets], env (prints to stdout for piping; fish shells get set -gx), verify. nism does not wrap or spawn your process: to inject secrets into a command, eval the env output first as shown.

Credentials resolve in order: CLI flags, then NISM_* env vars, then legacy NEW_INSTANCE_* env vars, then ~/.nism/config.json, then an interactive prompt. The App Secret is never written to the config file: supply it per run or via NISM_APP_SECRET. Point at the live platform with NISM_GRAPHQL_URL=https://service.newinstance.cloud/service (the built-in default targets a local dev server, so production runs must set this).

Load flags (defaults): --environment development, --format dotenv|json|shell, --output <file>, --bridge true with --bridge-storage-dir ~/.nism, --shell-integration true (detects bash, zsh, fish, ksh, dash), --auto-add-to-profile, --ide-integration, --create-env [file] (a bare flag means .env), --silent, --debug.

Bridge files in ~/.nism: <environment>.json and ide.env at mode 0600, load-<environment>.sh at 0700, config.json at 0600. --auto-add-to-profile appends a marked source block to the matching shell profile. --create-env writes into the current directory with default permissions, unlike the 0600 bridge files, so gitignore the output.

CI: npx nismsdk load --silent --create-env .env with NISM_* repository secrets; add --no-bridge in Docker builds.

Programmatic use (same package): loadSecrets(), injectSecrets(), NismGraphQLClient, decryptMasterKey, decryptWithMasterKey.

Troubleshooting: an invalid App Secret fails the key check; "all variables failed to decrypt" means the right app but the wrong secret; authentication failures point at the API key or org; a missing inheritance link key surfaces when a linked app was unshared; ECONNREFUSED means the GraphQL URL is wrong or unreachable. Re-run with --debug.

Authorization

x-api-keyAPI key · headerrequired

Parameters

environmentquery · stringdefault:

Optional. development, staging or production. Defaults to development.

appIdpath · stringrequireddefault:

Responses

200Encrypted variables
401Invalid or missing API key
403Missing permission scope
404App not found
curl -X GET 'https://api.newinstance.cloud/api/v1/secret-manager/apps/{appId}/variables?environment=production' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "success": true,
  "data": {
    "appId": "66f1a2b3c4d5e6f7a8b9c0a1",
    "environment": "production",
    "variables": [
      {
        "id": "66f1a2b3c4d5e6f7a8b9c0b1",
        "key": "DATABASE_URL",
        "value": "Zm9vYmFyLWNpcGhlcnRleHQtMQ==",
        "type": "String",
        "description": "Primary Postgres connection string",
        "environment": "production",
        "isInherited": false,
        "sourceAppId": null,
        "version": 3,
        "iv": "p2Lm8Qx1Zt7Rw4Ns",
        "tag": "Hc5vB8nM2xK9qL1sT4wY7g=="
      },
      {
        "id": "66f1a2b3c4d5e6f7a8b9c0b2",
        "key": "STRIPE_SECRET_KEY",
        "value": "Zm9vYmFyLWNpcGhlcnRleHQtMg==",
        "type": "String",
        "description": "Stripe live secret key",
        "environment": "production",
        "isInherited": false,
        "sourceAppId": null,
        "version": 1,
        "iv": "Tn6Wq3Lx9Zp2Rm5K",
        "tag": "Ld8sF3gH6jK1mN4pQ7rT0w=="
      }
    ]
  }
}

Session tokens (REST API)

5 endpoints

Get a customer's live session

GET/api/v1/chat/sessions

Returns the customer's live session, if they hold one.

Call it before minting to decide whether to reuse the token you have, wait, or revoke and re-mint. A customer can hold at most one session, so sessions is a list of zero or one.

Expired and revoked sessions are simply absent: the record exists only while the session is usable.

Authorization

x-api-keyAPI key · headerrequired

Parameters

customerIdquery · stringdefault:

Required. Your id for the customer to look up, the same customerId you minted the session with.

Responses

200Live session found
401Invalid or missing API key
curl -X GET 'https://api.newinstance.cloud/api/v1/chat/sessions?customerId=usr_123' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "sessions": [
    {
      "sessionId": "b3f1c2d4e5a67890b1c2d3e4f5a6b7c8",
      "customerId": "usr_123",
      "status": "active",
      "expiresAt": "2026-08-26T16:00:00.000Z",
      "expiresInSeconds": 21600
    }
  ]
}

Create a chat session

POST/api/v1/chat/sessions

Mints a chat session for one of your customers, from YOUR backend.

Call this wherever your app establishes a session (typically right after sign-in) and return the token to your frontend alongside your own session token. The frontend hands that one string to the chat SDK and passes nothing else.

Bodylink

FieldTypeRequiredNotes
customerIdstringYesYour stable id for this customer. Becomes the verified customer id. Max 200 characters.
customerNamestringYesName on the conversation and on every message they send. Max 200.
customerEmailstringNoAlso satisfies the merchant Require customer email setting, so the visitor is never asked. Max 320.
customerPhonestringNoShown to agents. Max 40.
metadataobjectNoExtra agent-visible context. At most 20 keys, 64-character keys, 500-character values, 4096 bytes total. Nested objects and arrays are rejected rather than flattened.
expiresInSecondsintegerNoYou choose the lifetime: 60 to 86400 seconds (24 hours max). Defaults to 3600. The example asks for 6 hours.

One live session per customerlink

A customer may hold one session at a time. While theirs is still valid this returns 409 with the session that is in the way. Once it expires, or you revoke it, a new one can be minted. That keeps a single identity from accumulating parallel credentials.

The 409 body carries the existing session, so you can decide whether to keep using the token you already have or revoke and re-mint.

What comes backlink

A signed JWT, and a session summary that is only an id, its owner and its expiry.

Everything about the customer is inside the token, signed. The platform keeps no copy: it would be duplicated state that could drift from the claims, and the JWT already answers the question. If you need the name, email, phone or metadata back, decode the token.

The token is never stored either. If you lose it, revoke the session and mint another.

Authorization

x-api-keyAPI key · headerrequired

Request body

application/json
{
  "customerId": "usr_123",
  "customerName": "Ada Lovelace",
  "customerEmail": "ada@example.com",
  "customerPhone": "+44 20 7946 0958",
  "metadata": {
    "plan": "enterprise",
    "accountNumber": "AC-4471"
  },
  "expiresInSeconds": 21600
}

Responses

201Session created
400Validation error
401Invalid or missing API key
409Customer already has a live session
curl -X POST 'https://api.newinstance.cloud/api/v1/chat/sessions' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "customerId": "usr_123",
  "customerName": "Ada Lovelace",
  "customerEmail": "ada@example.com",
  "customerPhone": "+44 20 7946 0958",
  "metadata": {
    "plan": "enterprise",
    "accountNumber": "AC-4471"
  },
  "expiresInSeconds": 21600
}'
Response
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJiM2YxYzJkNGU1YTY3ODkwIiwic3ViIjoidXNyXzEyMyJ9.7mK2_x1s0KQ",
  "expiresAt": "2026-08-26T16:00:00.000Z",
  "expiresInSeconds": 21600,
  "session": {
    "sessionId": "b3f1c2d4e5a67890b1c2d3e4f5a6b7c8",
    "customerId": "usr_123",
    "status": "active",
    "expiresAt": "2026-08-26T16:00:00.000Z",
    "expiresInSeconds": 21600
  }
}

Invalidate a customer's session

DELETE/api/v1/chat/sessions

Invalidates whatever session a customer holds, for sign-out when you did not keep the session id.

Idempotent: when the customer holds no session it still returns 200 with revoked: false, because "this customer has no live chat session" is the desired end state either way. Wire it into your own sign-out and you never have to track session ids.

Authorization

x-api-keyAPI key · headerrequired

Parameters

customerIdquery · stringdefault:

Required. Your id for the customer to sign out of chat.

Responses

200Session invalidated
401Invalid or missing API key
curl -X DELETE 'https://api.newinstance.cloud/api/v1/chat/sessions?customerId=usr_123' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "session": {
    "sessionId": "b3f1c2d4e5a67890b1c2d3e4f5a6b7c8",
    "customerId": "usr_123",
    "status": "active",
    "expiresAt": "2026-08-26T16:00:00.000Z",
    "expiresInSeconds": 21600
  },
  "revoked": true
}

Get one session

GET/api/v1/chat/sessions/{chatSessionId}

Reports whether this session has been invalidated.

chatSessionId in the path is the session.sessionId returned by Create a chat session; Postman stores it after that request runs.

Invalidation is the only thing a session id can answer, and the only thing worth asking. The token is a JWT: it carries its own exp and the customer's identity, so decode it for those. What the token cannot tell you is whether someone revoked it early, which is exactly what this reports.

revoked: false means no early invalidation was recorded. It does not mean the token is unexpired, because expiry is the token's business, not the platform's.

Authorization

x-api-keyAPI key · headerrequired

Parameters

chatSessionIdpath · stringrequireddefault:

Responses

200Not revoked
401Invalid or missing API key
curl -X GET 'https://api.newinstance.cloud/api/v1/chat/sessions/{chatSessionId}' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "sessionId": "b3f1c2d4e5a67890b1c2d3e4f5a6b7c8",
  "revoked": false
}

Invalidate a session

DELETE/api/v1/chat/sessions/{chatSessionId}

Invalidates a session before it expires.

chatSessionId in the path is the session.sessionId returned by Create a chat session.

The token stops being accepted on the next request: its signature is still valid and it has not expired, but the session is on the denylist. There is no propagation delay and no cache to wait out.

customerId is required so the customer's slot is freed as well as the token denied. Without it the token would die but the customer would stay blocked from a new session until natural expiry. You always have it: it is what you minted the session for.

Idempotent, so a retried sign-out is safe.

Authorization

x-api-keyAPI key · headerrequired

Parameters

customerIdquery · stringdefault:

Required. The customer this session belongs to, so their one-session slot is freed too.

chatSessionIdpath · stringrequireddefault:

Responses

200Session invalidated
401Invalid or missing API key
curl -X DELETE 'https://api.newinstance.cloud/api/v1/chat/sessions/{chatSessionId}?customerId=usr_123' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "sessionId": "b3f1c2d4e5a67890b1c2d3e4f5a6b7c8",
  "session": {
    "sessionId": "b3f1c2d4e5a67890b1c2d3e4f5a6b7c8",
    "customerId": "usr_123",
    "status": "active",
    "expiresAt": "2026-08-26T16:00:00.000Z",
    "expiresInSeconds": 21600
  },
  "revoked": true
}

EHR

14 endpoints

List reference codes

GET/api/v1/ehr/reference-codes

Searches ICD-10-CM, HCPCS, place of service, modifier, LOINC, RxNorm and taxonomy reference codes. Matches code prefix or display text substring, case insensitive, sorted by code. Any ehr product scope on the key is enough, no specific group is required.

Authorization

x-api-keyAPI key · headerrequired

Parameters

systemquery · stringdefault:

Optional. Code system: ICD10CM, HCPCS, POS, MODIFIER, LOINC, RXNORM or TAXONOMY. Omit it to search every system.

qquery · stringdefault:

Optional. Search text, 1 to 80 characters. Matches a code prefix or a display text substring, case insensitive.

limitquery · stringdefault:

Optional. Number of codes to return, 1 to 100. Defaults to 25.

Responses

200Reference codes
401Invalid or missing API key
403Missing permission scope
curl -X GET 'https://api.newinstance.cloud/api/v1/ehr/reference-codes?system=ICD10CM&q=chol&limit=25' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "items": [
    {
      "system": "ICD10CM",
      "code": "A00",
      "display": "Cholera"
    }
  ]
}

List webhook subscriptions

GET/api/v1/ehr/webhooks

Returns this organization's webhook subscriptions, newest first. Each item carries a masked secret preview, never the real secret.

Authorization

x-api-keyAPI key · headerrequired

Responses

200Webhook subscriptions
401Invalid or missing API key
403Missing permission scope
curl -X GET 'https://api.newinstance.cloud/api/v1/ehr/webhooks' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "items": [
    {
      "id": "66f1a2b3c4d5e6f7a8b9c0d1",
      "name": "Order sync",
      "url": "https://example.com/hooks/ehr",
      "events": [
        "ehr.program.created",
        "ehr.program.updated"
      ],
      "status": "active",
      "includePhi": false,
      "failureCount": 0,
      "secretMasked": "whsec_••••ab12",
      "createdAt": "2026-09-01T12:00:00.000Z",
      "updatedAt": "2026-09-01T12:00:00.000Z"
    }
  ]
}

Create a webhook subscription

POST/api/v1/ehr/webhooks

Creates a webhook subscription. The response includes the signing secret in clear text. Copy it now, it is never shown again. A key issued by a member whose role does not reach every client is refused, because a webhook is sent events about every client. The member who issued the key becomes the member the webhook is delivered under, and nothing more is sent to it once that member no longer reaches every client or leaves the organization, until a member who does saves it again.

Authorization

x-api-keyAPI key · headerrequired

Request body

application/json
{
  "name": "Order sync",
  "url": "https://example.com/hooks/ehr",
  "events": [
    "ehr.program.created",
    "ehr.program.updated"
  ],
  "includePhi": false
}

Responses

201Webhook subscription created
400Validation error
401Invalid or missing API key
403Missing permission scope
curl -X POST 'https://api.newinstance.cloud/api/v1/ehr/webhooks' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "name": "Order sync",
  "url": "https://example.com/hooks/ehr",
  "events": [
    "ehr.program.created",
    "ehr.program.updated"
  ],
  "includePhi": false
}'
Response
{
  "id": "66f1a2b3c4d5e6f7a8b9c0d1",
  "name": "Order sync",
  "url": "https://example.com/hooks/ehr",
  "events": [
    "ehr.program.created",
    "ehr.program.updated"
  ],
  "status": "active",
  "includePhi": false,
  "failureCount": 0,
  "secretMasked": "whsec_••••ab12",
  "secret": "whsec_5f2b9c1a7d3e4f6089ab12",
  "createdAt": "2026-09-01T12:00:00.000Z",
  "updatedAt": "2026-09-01T12:00:00.000Z"
}

Send a test event

POST/api/v1/ehr/webhooks/{ehrWebhookId}/test

Queues an ehr.webhook.test event and a pending delivery for this subscription, so you can confirm your receiver handles a delivery correctly. ehrWebhookId in the path is the id returned by Create a webhook subscription, which Postman stores after that request runs.

Authorization

x-api-keyAPI key · headerrequired

Parameters

ehrWebhookIdpath · stringrequireddefault:

Responses

202Test event queued
401Invalid or missing API key
403Missing permission scope
404Webhook subscription not found
curl -X POST 'https://api.newinstance.cloud/api/v1/ehr/webhooks/{ehrWebhookId}/test' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "eventId": "66f1a2b3c4d5e6f7a8b9c0d2",
  "deliveryId": "66f1a2b3c4d5e6f7a8b9c0d3"
}

List events

GET/api/v1/ehr/events

Returns this organization's event log in sequence order. Pass the previous nextSeq back as sinceSeq to continue from where you left off. An event that names a client protected by 42 CFR Part 2 or a client with no record, or that is about an appointment marked protected or one that cannot be found, is left out, and nextSeq still moves past it. A key issued by a member whose role does not reach every client, such as a clinician who reads a caseload, is refused before a record is read; issue the key from the front desk or an administrator.

Authorization

x-api-keyAPI key · headerrequired

Parameters

sinceSeqquery · stringdefault:

Optional. Return only events with a sequence number greater than this value. Pass the previous nextSeq to continue.

limitquery · stringdefault:

Optional. Number of events to return, 1 to 500. Defaults to 100.

Responses

200Event log page
401Invalid or missing API key
403Missing permission scope
curl -X GET 'https://api.newinstance.cloud/api/v1/ehr/events?sinceSeq=0&limit=100' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "items": [
    {
      "id": "66f1a2b3c4d5e6f7a8b9c0d2",
      "seq": 1,
      "type": "ehr.webhook.test",
      "occurredAt": "2026-09-01T12:00:00.000Z",
      "aggregate": {
        "type": "webhook_subscription",
        "id": "66f1a2b3c4d5e6f7a8b9c0d1"
      },
      "status": "pending"
    }
  ],
  "nextSeq": 1
}

List appointments

GET/api/v1/ehr/appointments

Returns appointments that overlap the requested range, oldest first. Every instant is ISO 8601 in UTC and each appointment carries the IANA zone it was booked in, so a client in another zone can render the local wall time correctly. Needs the scheduling scope. A key issued by a member whose role does not reach every client, such as a clinician who reads a caseload, is refused before a record is read; issue the key from the front desk or an administrator.

Authorization

x-api-keyAPI key · headerrequired

Parameters

fromquery · stringdefault:

Required. Range start, an ISO 8601 instant.

toquery · stringdefault:

Required. Range end, an ISO 8601 instant.

pagequery · stringdefault:

Optional. Page number, from 1. Defaults to 1.

limitquery · stringdefault:

Optional. Appointments per page, 1 to 100. Defaults to 25.

Responses

200Appointment page
400Validation error
401Invalid or missing API key
403Missing permission scope
curl -X GET 'https://api.newinstance.cloud/api/v1/ehr/appointments?from=2026-10-25T00%3A00%3A00.000Z&to=2026-10-26T00%3A00%3A00.000Z&page=1&limit=25' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "items": [
    {
      "id": "66f1a2b3c4d5e6f7a8b9c0e1",
      "seriesId": null,
      "typeId": "66f1a2b3c4d5e6f7a8b9c0a1",
      "status": "scheduled",
      "clientIds": [
        "66f1a2b3c4d5e6f7a8b9c0b1"
      ],
      "primaryStaffId": "66f1a2b3c4d5e6f7a8b9c0c1",
      "staffIds": [
        "66f1a2b3c4d5e6f7a8b9c0c1"
      ],
      "roomLocationId": "66f1a2b3c4d5e6f7a8b9c0d4",
      "locationId": null,
      "programId": null,
      "serviceCodeId": null,
      "start": "2026-10-25T11:00:00.000Z",
      "end": "2026-10-25T12:00:00.000Z",
      "timezone": "America/New_York",
      "checkInAt": null,
      "checkOutAt": null,
      "encounterId": null
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 25
}

Create an appointment

POST/api/v1/ehr/appointments

Books an appointment. Send start and end in UTC and the zone the time was chosen in. A clash returns 409 naming the resource already booked, unless the organization double-booking policy allows it or you send allowConflicts. A client, staff member, room, program or appointment type that cannot take the booking is refused with 4xx and a message saying what to change; a 503 means the service could not confirm its own indexes and the request is worth retrying. A key issued by a member whose role does not reach every client, such as a clinician who reads a caseload, is refused before a record is read; issue the key from the front desk or an administrator.

Authorization

x-api-keyAPI key · headerrequired

Request body

application/json
{
  "typeId": "66f1a2b3c4d5e6f7a8b9c0a1",
  "clientIds": [
    "66f1a2b3c4d5e6f7a8b9c0b1"
  ],
  "primaryStaffId": "66f1a2b3c4d5e6f7a8b9c0c1",
  "start": "2026-10-26T13:00:00.000Z",
  "end": "2026-10-26T14:00:00.000Z",
  "timezone": "America/New_York"
}

Responses

201Appointment booked
400Validation error
401Invalid or missing API key
403Missing permission scope
409Booking clash
curl -X POST 'https://api.newinstance.cloud/api/v1/ehr/appointments' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "typeId": "66f1a2b3c4d5e6f7a8b9c0a1",
  "clientIds": [
    "66f1a2b3c4d5e6f7a8b9c0b1"
  ],
  "primaryStaffId": "66f1a2b3c4d5e6f7a8b9c0c1",
  "start": "2026-10-26T13:00:00.000Z",
  "end": "2026-10-26T14:00:00.000Z",
  "timezone": "America/New_York"
}'
Response
{
  "id": "66f1a2b3c4d5e6f7a8b9c0e2",
  "seriesId": null,
  "typeId": "66f1a2b3c4d5e6f7a8b9c0a1",
  "status": "scheduled",
  "clientIds": [
    "66f1a2b3c4d5e6f7a8b9c0b1"
  ],
  "primaryStaffId": "66f1a2b3c4d5e6f7a8b9c0c1",
  "staffIds": [
    "66f1a2b3c4d5e6f7a8b9c0c1"
  ],
  "roomLocationId": null,
  "locationId": null,
  "programId": null,
  "serviceCodeId": null,
  "start": "2026-10-26T13:00:00.000Z",
  "end": "2026-10-26T14:00:00.000Z",
  "timezone": "America/New_York",
  "checkInAt": null,
  "checkOutAt": null,
  "encounterId": null
}

List claims

GET/api/v1/ehr/billing/claims

Lists claims newest first with what each one is worth and whether the clearinghouse answered. Needs the billing-read group on the key and a creator who holds the permission to view billing amounts. Claims for a client protected by 42 CFR Part 2 are in neither the rows nor the total. Payer reason codes are on the single claim read, never here. A key issued by a member whose role does not reach every client, such as a clinician who reads a caseload, is refused before a record is read; issue the key from the front desk or an administrator.

Authorization

x-api-keyAPI key · headerrequired

Parameters

pagequery · stringdefault:

Optional. Page number, from 1. Defaults to 1.

limitquery · stringdefault:

Optional. Claims per page, 1 to 100. Defaults to 25.

Responses

200Claims
401Invalid or missing API key
403Missing permission scope
curl -X GET 'https://api.newinstance.cloud/api/v1/ehr/billing/claims?page=1&limit=25' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "items": [
    {
      "id": "66f1a2b3c4d5e6f7a8b9c0e1",
      "payerId": "66f1a2b3c4d5e6f7a8b9c0e2",
      "controlNumber": "10001",
      "status": "queued",
      "chargeCount": 1,
      "totalAmountMinor": 12500,
      "currency": "USD",
      "transmission": {
        "status": "acknowledged",
        "sentAt": "2026-09-02T11:00:00.000Z",
        "acknowledgedAt": "2026-09-03T11:00:00.000Z"
      },
      "submittedAt": "2026-09-02T10:00:00.000Z"
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 25
}

Read one claim

GET/api/v1/ehr/billing/claims/{ehrClaimId}

Returns one claim with its service lines, the state of its transmission, and the payer reason and remark codes where the payer denied it, with deniedAt, the date the payer gave for the denial, as that calendar date in the form YYYY-MM-DD, such as 2026-09-11, with no time of day. The claim file itself, which carries the member number, the name, the date of birth and the diagnoses, is never returned. A claim for a Part 2 client answers 404. A key issued by a member whose role does not reach every client, such as a clinician who reads a caseload, is refused before a record is read; issue the key from the front desk or an administrator. ehrClaimId in the path is a claim id from List claims, which Postman stores after that request runs.

Authorization

x-api-keyAPI key · headerrequired

Parameters

ehrClaimIdpath · stringrequireddefault:

Responses

200Claim
401Invalid or missing API key
403Missing permission scope
404Claim not found
curl -X GET 'https://api.newinstance.cloud/api/v1/ehr/billing/claims/{ehrClaimId}' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "id": "66f1a2b3c4d5e6f7a8b9c0e1",
  "payerId": "66f1a2b3c4d5e6f7a8b9c0e2",
  "controlNumber": "10001",
  "payerClaimControlNumber": null,
  "status": "queued",
  "format": "837P",
  "frequencyCode": "1",
  "totalAmountMinor": 12500,
  "currency": "USD",
  "transmission": {
    "status": "acknowledged",
    "sentAt": "2026-09-02T11:00:00.000Z",
    "acknowledgedAt": "2026-09-03T11:00:00.000Z"
  },
  "lines": [
    {
      "lineNo": 1,
      "chargeId": "66f1a2b3c4d5e6f7a8b9c0e3",
      "serviceCode": "90834",
      "modifiers": [],
      "units": 1,
      "amountMinor": 12500,
      "dateOfService": "2026-09-01"
    }
  ],
  "denial": {
    "status": "open",
    "reasons": [
      "CO-97"
    ],
    "remarkCodes": [
      "N19"
    ],
    "deniedMinor": 2500,
    "allowedMinor": 10000,
    "deniedAt": "2026-09-11"
  },
  "submittedAt": "2026-09-02T10:00:00.000Z",
  "updatedAt": "2026-09-02T10:00:00.000Z"
}

Read one charge and its ledger

GET/api/v1/ehr/billing/charges/{ehrChargeId}

Returns one charge and every ledger entry posted against it, with what each party still owes on it: payerOwedMinor for the insurer, clientOwedMinor for the client, and transferredToClientMinor for what has moved from one to the other. There is no single party blind balance, because once responsibility has moved between the two a single figure names neither of them. The entry count is there because nothing owed is also what a charge nothing ever posted to looks like. Diagnoses are not returned. The figures are worked out from the entries returned beside them when the charge is read, so the two always agree. A charge for a Part 2 client answers 404. A key issued by a member whose role does not reach every client, such as a clinician who reads a caseload, is refused before a record is read; issue the key from the front desk or an administrator. ehrChargeId in the path is a chargeId from the lines of Read one claim.

Authorization

x-api-keyAPI key · headerrequired

Parameters

ehrChargeIdpath · stringrequireddefault:

Responses

200Charge and ledger
401Invalid or missing API key
403Missing permission scope
404Charge not found
curl -X GET 'https://api.newinstance.cloud/api/v1/ehr/billing/charges/{ehrChargeId}' \
  -H 'x-api-key: YOUR_API_KEY'
Response
{
  "id": "66f1a2b3c4d5e6f7a8b9c0e3",
  "serviceCode": "90834",
  "modifiers": [],
  "units": 1,
  "dateOfService": "2026-09-01",
  "amountMinor": 12500,
  "currency": "USD",
  "status": "ready",
  "payerOwedMinor": 0,
  "clientOwedMinor": 2500,
  "transferredToClientMinor": 2500,
  "entryCount": 4,
  "entries": [
    {
      "kind": "charge",
      "sourceType": "charge",
      "amountMinor": 12500,
      "currency": "USD",
      "payerId": "66f1a2b3c4d5e6f7a8b9c0e2",
      "postedAt": "2026-09-01T12:00:00.000Z"
    },
    {
      "kind": "payer_payment",
      "sourceType": "remittance_line",
      "amountMinor": 8000,
      "currency": "USD",
      "payerId": "66f1a2b3c4d5e6f7a8b9c0e2",
      "postedAt": "2026-09-10T12:00:00.000Z"
    },
    {
      "kind": "contractual_adjustment",
      "sourceType": "remittance_line",
      "amountMinor": 2000,
      "currency": "USD",
      "payerId": "66f1a2b3c4d5e6f7a8b9c0e2",
      "postedAt": "2026-09-10T12:00:00.000Z"
    },
    {
      "kind": "transfer_to_client",
      "sourceType": "remittance_line",
      "amountMinor": 2500,
      "currency": "USD",
      "payerId": null,
      "postedAt": "2026-09-10T12:00:00.000Z"
    }
  ],
  "capturedAt": "2026-09-01T12:00:00.000Z"
}

Look up a client by MRN

POST/api/v1/ehr/clients/lookup

Returns the demographic summary for one client by medical record number. The number is sent in the request body, never in the path or the query string, because those reach an access log. Needs the clients-read scope, and a key issued by a member whose role reaches every client, such as the front desk or an administrator; any other key is refused before a record is read. Every call is written to the access log with the API key as the actor.

Authorization

x-api-keyAPI key · headerrequired

Request body

application/json
{
  "mrn": ""
}

Responses

200Client summary
401Invalid or missing API key
403Missing permission scope
404No such client
curl -X POST 'https://api.newinstance.cloud/api/v1/ehr/clients/lookup' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "mrn": ""
}'
Response
{
  "id": "66f1a2b3c4d5e6f7a8b9c0b1",
  "mrn": "MRN-1042",
  "firstName": "Ada",
  "lastName": "Okafor",
  "dob": "1984-03-09",
  "status": "active",
  "part2": false,
  "primaryProgramId": "66f1a2b3c4d5e6f7a8b9c0f4",
  "primaryClinicianStaffId": "66f1a2b3c4d5e6f7a8b9c0c1"
}

Inbound SMS webhook

POST/api/v1/ehr/sms/inbound

Receives the message replies your SMS provider posts. The request carries the provider signature, so no API key is used. A signature that does not verify answers 401 with an empty body. A reply of C, CONFIRM, YES or Y confirms the next appointment within seven days, except from a number more than one client shares, where it confirms nothing and the reply is recorded as ignored. STOP, STOPALL, UNSUBSCRIBE, CANCEL, END or QUIT removes text message consent for that number immediately, so a reply of CANCEL opts the number out of text messages and cancels no appointment.

Headers

x-twilio-signature

Request body

application/x-www-form-urlencoded
{
  "To": "+15550100000",
  "From": "+15550101234",
  "Body": "C",
  "MessageSid": "SM00000000000000000000000000000001"
}

Responses

204Reply handled
400Validation error
401Signature did not verify, empty body
404No organization owns that number
curl -X POST 'https://api.newinstance.cloud/api/v1/ehr/sms/inbound' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-raw '{
  "To": "+15550100000",
  "From": "+15550101234",
  "Body": "C",
  "MessageSid": "SM00000000000000000000000000000001"
}'
Response
null

Delete a webhook subscription

DELETE/api/v1/ehr/webhooks/{ehrWebhookId}

Soft deletes a webhook subscription. It stops receiving deliveries immediately and drops out of the list. ehrWebhookId in the path is the id returned by Create a webhook subscription. A key issued by a member whose role does not reach every client is refused, because a webhook is sent events about every client.

Authorization

x-api-keyAPI key · headerrequired

Parameters

ehrWebhookIdpath · stringrequireddefault:

Responses

204Subscription deleted
401Invalid or missing API key
403Missing permission scope
404Webhook subscription not found
curl -X DELETE 'https://api.newinstance.cloud/api/v1/ehr/webhooks/{ehrWebhookId}' \
  -H 'x-api-key: YOUR_API_KEY'
Response
null

Update a webhook subscription

PATCH/api/v1/ehr/webhooks/{ehrWebhookId}

Updates one or more fields on a webhook subscription: name, url, events, or status. At least one field is required. ehrWebhookId in the path is the id returned by Create a webhook subscription. A key issued by a member whose role does not reach every client is refused, because a webhook is sent events about every client. The member who issued the key becomes the member the webhook is delivered under, and nothing more is sent to it once that member no longer reaches every client or leaves the organization, until a member who does saves it again.

Authorization

x-api-keyAPI key · headerrequired

Parameters

ehrWebhookIdpath · stringrequireddefault:

Request body

application/json
{
  "status": "paused"
}

Responses

200Webhook subscription updated
400Validation error
401Invalid or missing API key
403Missing permission scope
404Webhook subscription not found
curl -X PATCH 'https://api.newinstance.cloud/api/v1/ehr/webhooks/{ehrWebhookId}' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "status": "paused"
}'
Response
{
  "id": "66f1a2b3c4d5e6f7a8b9c0d1",
  "name": "Order sync",
  "url": "https://example.com/hooks/ehr",
  "events": [
    "ehr.program.created",
    "ehr.program.updated"
  ],
  "status": "paused",
  "includePhi": false,
  "failureCount": 0,
  "secretMasked": "whsec_••••ab12",
  "createdAt": "2026-09-01T12:00:00.000Z",
  "updatedAt": "2026-09-01T12:05:00.000Z"
}

AI Agent Access (MCP)

1 endpoint

List available tools

POST/mcp

Lists the tools this key can use, and documents the tools/call request that runs one. Keys scoped to BugWatch (or FULL_ACCESS) see the three BugWatch tools; a key with no matching product scopes sees an empty list.

The answer is result.tools[], each tool with name, description and a JSON Schema inputSchema, sent as a single SSE frame: parse the JSON after data:.

Call a toollink

Send tools/call to the same URL with the tool name and its arguments. Tool results come back MCP-style: result.content[0].text contains the JSON payload as a string, so parse it client-side. Tool-level failures set result.isError: true with the message in the text block.

bugwatch_list_projectslink

Lists the organization's BugWatch projects, newest first. Each project carries its id, name, slug, publicId, environments and settings, and the result adds totalCount and paging fields. search filters by name, case-insensitive; limit is 1 to 50 and defaults to 10.

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "bugwatch_list_projects",
    "arguments": {
      "search": "",
      "page": 1,
      "limit": 20
    }
  }
}

bugwatch_list_issueslink

Lists the issues in one project, sorted by lastSeen unless sort is firstSeen or occurrences. Each issue carries its id, title, level, status, culprit, occurrence count, firstSeen and lastSeen. query (optional) matches title or culprit; status filters by unresolved, resolved or ignored; limit is 1 to 100 and defaults to 20.

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "bugwatch_list_issues",
    "arguments": {
      "projectId": "{{projectId}}",
      "status": "unresolved",
      "page": 1,
      "limit": 20
    }
  }
}

bugwatch_root_cause_analysislink

Runs the platform AI root-cause analysis for one issue. Use the same call shape with { "name": "bugwatch_root_cause_analysis", "arguments": { "projectId": "…", "issueId": "…" } }. The text block holds { analysis, contextSummary, error }; when error is set the analysis failed, but contextSummary is still real incident data.

Authorization

x-api-keyAPI key · headerrequired

Headers

Accept

Request body

application/json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list"
}

Responses

200Tools listed
401Not a server key
curl -X POST 'https://api.newinstance.cloud/mcp' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list"
}'
Response
event: message
data: {"result":{"tools":[{"name":"bugwatch_list_projects","description":"List the BugWatch projects of the organization that owns the API key. Returns projects with id, name, platform, and counts, plus totalCount and pagination info. Results are paginated (default 10 per page); check hasNextPage and fetch further pages before concluding a project does not exist.","inputSchema":{"type":"object","properties":{"search":{"description":"Case-insensitive project name filter","type":"string"},"page":{"type":"integer","minimum":1,"maximum":9007199254740991},"limit":{"type":"integer","minimum":1,"maximum":50}},"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"}},{"name":"bugwatch_list_issues","description":"List error/issue groups in one BugWatch project (get projectId from bugwatch_list_projects). Each issue has id, title, level, status, culprit, occurrence counts, and firstSeen/lastSeen. Results are paginated (default 20 per page) and sorted by lastSeen unless sort is given; never claim an issue is absent from sampled or truncated results; page through or narrow with query.","inputSchema":{"type":"object","properties":{"projectId":{"description":"BugWatch project id","type":"string"},"status":{"description":"Filter: unresolved, resolved, or ignored (OPEN also means unresolved)","type":"string"},"query":{"description":"Case-insensitive match on issue title or culprit","type":"string"},"env":{"type":"string"},"sort":{"description":"lastSeen (default), firstSeen, or occurrences","type":"string"},"page":{"type":"integer","minimum":1,"maximum":9007199254740991},"limit":{"type":"integer","minimum":1,"maximum":100}},"required":["projectId"],"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"}},{"name":"bugwatch_root_cause_analysis","description":"Run the platform AI root-cause analysis for one BugWatch issue. Returns { analysis, contextSummary, error }: analysis is the AI-generated root cause and next steps, contextSummary is the deterministic incident context it was based on. If error is set, generation failed (e.g. no AI provider configured), so report that failure honestly; the contextSummary is still real incident data you may reason over yourself.","inputSchema":{"type":"object","properties":{"projectId":{"description":"BugWatch project id","type":"string"},"issueId":{"description":"Issue id from bugwatch_list_issues","type":"string"}},"required":["projectId","issueId"],"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"}}]},"jsonrpc":"2.0","id":1}