Skip to content
View as Markdown

Errors and result codes

SocialScope reports problems in four places: GraphQL errors on a request, resultCode on a Connect attempt, errorCode on a sync job, and reason or incompleteReason on data. This page lists every value a consumer can see and what your app should do about it. Treat any code you do not recognize as a retryable failure that shows no success, and log its correlationId.

GraphQL error shape

A failed request returns an errors array. Each error looks like this:

{
  "errors": [
    {
      "message": "RATE_LIMITED",
      "path": ["requestRefresh"],
      "extensions": {
        "code": "RATE_LIMITED",
        "correlationId": "4f1c2a9e-0b7d-4e7a-9a51-2a3f0c1d9e88",
        "retryAfterSeconds": 60
      }
    }
  ],
  "data": null
}
  • extensions.code is one of the codes below. Branch on it, not on message.
  • message is the code itself, except for the request-limit messages GraphQL alias limit exceeded, GraphQL depth limit exceeded, GraphQL cost limit exceeded, GraphQL operation limit exceeded and Operation batching disabled. Any other detail, such as a parse or validation error naming a bad field, is replaced by the bare code.
  • extensions.correlationId is a UUID on every error. Log it and quote it when you ask for help.
  • extensions.retryAfterSeconds appears only on some RATE_LIMITED errors.
  • Do not rely on the HTTP status. Most failed operations arrive with HTTP 200. Parse, validation, limit and batching errors arrive with HTTP 400 and the same errors shape. A body over 64 KiB gets HTTP 413 before GraphQL runs, with no GraphQL body.

GraphQL error codes

Code Where Meaning What to do
UNAUTHENTICATED Any authenticated operation The key is missing, malformed, unknown, revoked or expired Check configuration. Ask the admin for a new key. Do not retry in a loop
FORBIDDEN Any Your app is disabled, or the tenant, provider, capability or registration is outside your app's policy. Also a connection whose policy changed. On createConnectSession it can also be transient, while a disconnect at the same provider settles in production Treat the feature as unavailable for now and retry later. If it persists, ask the admin
NOT_FOUND Reads and mutations The attempt, connection, post or job does not exist, belongs to another scope or app, or has expired. Also an unregistered tenant on most reads Refresh your local state. Never reveal whether another user's item exists
BAD_USER_INPUT Any Invalid window, first, URL, capability list, scope length or return URL. Also a query that does not parse or does not match the schema, such as a misspelled field, and a request over a limit. The message does not say which field is wrong Fix the request. This is a bug, not a transient error. Validate your queries against /schema.graphql locally, for example with graphql-js validate(), because the error will not name the field
STALE_CURSOR connections, content, requestRefresh A cursor is tampered with, from another owner or window, expired, or from before a reconnect Restart from the first page or a new refresh
SYNC_WINDOW_BUSY requestRefresh A scan for a different window is already queued or running on this connection Wait for that job, then retry
RATE_LIMITED createConnectSession, createGoogleIdentitySession, requestRefresh, requestContentLookup A cap was reached. See Rate limits Wait retryAfterSeconds when present. Otherwise back off and retry later
RECONNECT_REQUIRED Reads and refreshes The connection is not CONNECTED, its grant stopped working, or a reconnect target cannot be reconnected Show a reconnect action. See Connect
AUTHORIZATION_SUSPENDED Reads and refreshes The connection is SUSPENDED, or SocialScope paused the provider registration Show "temporarily unavailable". Retry later. If it persists, ask the SocialScope team
CAPABILITY_UNAVAILABLE Reads, refreshes, identity start The connection lacks the capability, the provider no longer serves your app, or Google identity is unavailable Hide the feature for this connection
DISCONNECT_IN_PROGRESS createConnectSession SocialScope is still removing an earlier connection for this subject and provider Show "try again later". No attempt was created
UPSTREAM_UNAVAILABLE Any An unexpected failure inside SocialScope or at a provider Retry with backoff. Show a safe error, never inferred success

Reads by connection status

Connection.status connection, connections content, contentItem, requestRefresh, requestContentLookup
CONNECTED Full row, or an error when the row is no longer readable (below) Allowed when the capability is available, otherwise the same errors
SUSPENDED Row with no profile AUTHORIZATION_SUSPENDED
RECONNECT_REQUIRED Row with no profile RECONNECT_REQUIRED
DISCONNECTED Row with no profile, until erased RECONNECT_REQUIRED
Erased NOT_FOUND NOT_FOUND

A CONNECTED connection is checked again on every read. connection then throws instead of returning the row when:

Code Why Per-account state to show
AUTHORIZATION_SUSPENDED SocialScope paused the provider registration Temporarily unavailable
RECONNECT_REQUIRED The grant is no longer active, or its refresh credential expired Needs reconnect
CAPABILITY_UNAVAILABLE The provider registration no longer serves your app Unavailable
FORBIDDEN Your app's policy no longer allows this tenant, provider or capability Unavailable

connections reads every row on the page the same way, so one such account makes the whole list call fail with that code. Keep the connection IDs you received from Connect in your own database, read each one with connection, and map each error code to that account's state. Treat connections as a convenience. If it fails, fall back to per-ID reads rather than showing "no accounts".

Local codes from the sample helper

The sample helper in Authentication adds codes of its own. SocialScope never sends them.

Code Meaning
SOCIALSCOPE_NOT_CONFIGURED The GraphQL URL or key is missing from your server environment
SOCIALSCOPE_REQUEST_TOO_LARGE The request body was over 64 KiB and got HTTP 413
SOCIALSCOPE_UNAVAILABLE Network failure, timeout, or a response that was not GraphQL
SOCIALSCOPE_JOB_TIMEOUT The polling helper gave up waiting for a job

Rate limits

Limit Error
5 unfinished Connect attempts per subject and provider RATE_LIMITED from createConnectSession, no retry hint. Attempts stop counting once they finish or pass their 10-minute deadline
20 unfinished Google identity attempts per app RATE_LIMITED with retryAfterSeconds from 1 to 600
20 queued or running read jobs per app RATE_LIMITED with retryAfterSeconds: 60
One scan window at a time per connection SYNC_WINDOW_BUSY

There is no minimum interval between refreshes, but repeated requests for the same window or post while one is in flight return the existing job.

Connect result codes

connectSessionResult returns status, resultCode and creatorAction.

status resultCode Meaning
COMPLETED CONNECTED The attempt committed a connection. Read connection for its current status
DENIED PROVIDER_DENIED The person declined at the provider
EXPIRED SESSION_EXPIRED The 10-minute authorization deadline passed
CANCELED USER_CANCELED The person cancelled on SocialScope's page
CANCELED RECONNECT_REQUIRED SocialScope stopped the attempt while a disconnect at the same provider settled
FAILED POLICY_WITHDRAWN Your app's access changed during the attempt
FAILED DISCONNECT_IN_PROGRESS The chosen account is still being removed for this subject
FAILED ACCOUNT_NOT_ELIGIBLE Wrong account type, such as a personal Instagram account or a Google account without exactly one channel
FAILED ACCOUNT_MISMATCH The provider returned inconsistent account details
FAILED MISSING_REQUIRED_PERMISSION The grant lacks a permission SocialScope needs
FAILED OFFLINE_ACCESS_REQUIRED The provider did not issue a refresh token
FAILED RECONNECT_REQUIRED A reconnect picked a different account, or the target changed during the attempt
FAILED RATE_LIMITED The provider or SocialScope was busy with another token operation
FAILED UPSTREAM_UNAVAILABLE The provider did not answer the code exchange, or another failure
FAILED CAPABILITY_UNAVAILABLE The provider registration stopped serving your app during the attempt

For every non-COMPLETED result, offer a retry in your app. Never fall back to your own provider OAuth.

creatorAction is null or REMOVE_APP_AT_PROVIDER. When set, ask the person to remove the app in their provider account settings, because a grant may still be active there. Treat any unknown value the same way. It can change from null to REMOVE_APP_AT_PROVIDER on a later read of the same attempt.

Sync job error codes

syncJob.errorCode explains a FAILED job, and in one case a SUCCEEDED one. A QUEUED job that is waiting to retry also carries the last error in errorCode, with nextRetryAt set. That is not a final failure.

errorCode Job kind Meaning and action
CONTENT_NOT_OWNED Lookup The post belongs to another account. Tell the person
CONTENT_UNAVAILABLE Lookup The provider did not return the post. It may be private, deleted or not theirs
LOOKUP_INCOMPLETE Lookup Instagram only. The post is not among the 100 most recent
BAD_PROVIDER_ID Lookup The URL's ID is not valid for that provider
RECONNECT_REQUIRED Any read The grant stopped working. Offer a reconnect
CAPABILITY_UNAVAILABLE Any read The capability or provider is no longer available for this connection
ACCOUNT_MISMATCH Any read The provider now reports a different account for this grant. Offer a reconnect
MISSING_REQUIRED_PERMISSION Any read The grant lost a permission. Offer a reconnect
UPSTREAM_UNAVAILABLE Any The provider kept failing after retries. Try again later
RATE_LIMITED Any The provider kept rate limiting after retries. Try again later
UPSTREAM_REVOCATION_UNVERIFIED Revoke SocialScope could not confirm the revoke. Always the case for Instagram. creatorAction is set
ALREADY_INVALID Revoke, on SUCCEEDED The provider said the token was already invalid. The grant may still exist, so creatorAction is set

Availability reasons

On a MetricObservation:

availability reason Meaning
AVAILABLE null value holds the count
UNSUPPORTED PROVIDER_UNSUPPORTED The provider does not offer this metric for this item
UNAVAILABLE PROVIDER_OMITTED The provider did not return the value
UNAVAILABLE PROVIDER_INVALID The provider returned something that was not a count
UNAVAILABLE CONTENT_NOT_OWNED, CONTENT_UNAVAILABLE, LOOKUP_INCOMPLETE, BAD_PROVIDER_ID A later metrics refresh or lookup could not read the post. value is null

On a ContentItem:

availability reason Meaning
AVAILABLE null Normal
UNAVAILABLE EXPIRED Last observed more than 30 days ago. Fields are null. Refresh it
UNAVAILABLE CONTENT_NOT_OWNED A lookup found it belongs to another account
UNAVAILABLE CONTENT_UNAVAILABLE, LOOKUP_INCOMPLETE, BAD_PROVIDER_ID A metrics refresh or lookup could not read it

In every case except AVAILABLE, show "not available", never 0.

Completeness reasons

pageInfo.incompleteReason and syncJob.incompleteReason:

Value Meaning
NOT_FULLY_SCANNED No qualifying scan covers the window
SCAN_BOUNDED The scan hit its page or item limit
MISSING_PUBLICATION_DATE An item had no publication date
CONTENT_UNAVAILABLE An item could not be read

A syncJob for a metrics refresh or lookup that could not read its post also sets completeForWindow: false and incompleteReason to CONTENT_NOT_OWNED, CONTENT_UNAVAILABLE, LOOKUP_INCOMPLETE or BAD_PROVIDER_ID. Treat any other value as incomplete too.

See Reading data.

Google identity outcomes

redeemGoogleIdentityResult.status is PENDING, COMPLETED, DENIED, FAILED or EXPIRED. A second redeem, a wrong proof or tenant, another app's attempt, an expired result, or identity turned off for your app all return the same NOT_FOUND. See Google identity.

Browser-side failures

These happen in the person's browser on SocialScope's host, not in your GraphQL calls. You learn about them by reading the attempt from your server.

Failure What the person sees What your server reads
Launch posted from the wrong origin, or a reused or expired token A 403 with a small JSON body The attempt stays unfinished, then EXPIRED
Consent opened in a different browser or profile A 403 at the callback Unfinished, then EXPIRED
Provider returned an unexpected error A 403 at the callback Unfinished, then EXPIRED
Your return URL was removed during the attempt A static "Return to the app you started from" page The result is still readable

In each case, let the person start again from your app. An unfinished attempt reads as EXPIRED once its 10-minute deadline passes.