ARCY AI

Troubleshooting

Fix each Verify installation verdict, a widget that does not show, a blocking Content Security Policy, attributes that go nowhere, and a crawl that fails.

arcy.js never throws into your Product. Every problem it hits becomes a console.warn prefixed [arcy], so open the browser console first: the line there names the problem. Every warning's exact text, cause and fix is in the Console warnings table; the sections below cover the problems that take more than one line to fix.

The sections are numbered so you can point at one, and the numbers match on the Turkish page.

1. The widget does not show up

Look for this first:

[arcy] Failed to load. ARCY is disabled.

The script itself never loaded. An ad blocker, your Content Security Policy (section 2), or a page that navigated away before the script finished are the usual causes.

Without that line, arcy.js loaded but did not mount. Two messages say why:

[arcy] The page has no <body> to mount into yet. The widget will not render.

init() ran before the body existed, usually a script in the head without defer or async. Move the script tag to the end of the body, or call arcy.init() after the DOM is ready.

[arcy] This ARCY surface is already on this page. Ignoring the second mount.
[arcy] Another copy of ARCY is already on this page. Ignoring this one.

The snippet is installed twice: the npm path and the HTML path on one page, or the HTML snippet pasted into both a layout and a tag manager. Keep one install path and remove the other.

2. Your Content Security Policy is blocking the widget

The widget reports the whole fix once, the first time your policy blocks something of ours:

[arcy] This page's Content-Security-Policy is blocking part of the ARCY widget.
Add these to the policy your app sends:
  connect-src https://api.arcyai.com
  font-src    https://api.arcyai.com
  img-src     https://api.arcyai.com blob:
  style-src   'unsafe-inline', or a nonce on the script that loads ARCY

On the HTML snippet path the list also carries script-src https://cdn.arcyai.com, because the snippet is a script tag pointing there. On the npm path the chat panel, the flow engine and the picker are part of your own bundle and need nothing.

A directive you did not write is not enforced, and nothing falls back except through default-src. So a policy with no default-src needs nothing added, and a policy with one needs the lines above. Your full policy is the Content-Security-Policy response header on the document request in the browser's network tab.

The blob: in img-src is what lets a visitor see the image they attached before they send it.

Where to put it

If you already build the policy in middleware.ts, add the directives there. Otherwise:

js
// next.config.js
const csp = [
  "connect-src 'self' https://api.arcyai.com",
  "font-src 'self' https://api.arcyai.com",
  "img-src 'self' blob: https://api.arcyai.com",
].join("; ")

module.exports = {
  async headers() {
    return [
      {
        source: "/:path*",
        headers: [{ key: "Content-Security-Policy", value: csp }],
      },
    ]
  },
}
ts
// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    "/**": {
      headers: {
        "Content-Security-Policy": [
          "connect-src 'self' https://api.arcyai.com",
          "font-src 'self' https://api.arcyai.com",
          "img-src 'self' blob: https://api.arcyai.com",
        ].join("; "),
      },
    },
  },
})

SvelteKit generates the policy itself, so add the directives to the config:

js
// svelte.config.js
export default {
  kit: {
    csp: {
      directives: {
        "connect-src": ["self", "https://api.arcyai.com"],
        "font-src": ["self", "https://api.arcyai.com"],
        "img-src": ["self", "blob:", "https://api.arcyai.com"],
      },
    },
  },
}
ts
// src/middleware.ts
export const onRequest = async (context, next) => {
  const response = await next()
  response.headers.set(
    "Content-Security-Policy",
    [
      "connect-src 'self' https://api.arcyai.com",
      "font-src 'self' https://api.arcyai.com",
      "img-src 'self' blob: https://api.arcyai.com",
    ].join("; "),
  )
  return response
}
ts
// app/entry.server.tsx
responseHeaders.set(
  "Content-Security-Policy",
  [
    "connect-src 'self' https://api.arcyai.com",
    "font-src 'self' https://api.arcyai.com",
    "img-src 'self' blob: https://api.arcyai.com",
  ].join("; "),
)
js
app.use(
  helmet.contentSecurityPolicy({
    directives: {
      connectSrc: ["'self'", "https://api.arcyai.com"],
      fontSrc: ["'self'", "https://api.arcyai.com"],
      imgSrc: ["'self'", "blob:", "https://api.arcyai.com"],
    },
  }),
)

If you cannot set a header, set the policy in a <meta> tag in the head, before every script on the page:

html
<meta
  http-equiv="Content-Security-Policy"
  content="connect-src 'self' https://api.arcyai.com; font-src 'self' https://api.arcyai.com; img-src 'self' blob: https://api.arcyai.com"
/>

The panel renders unstyled

The widget adds its own style elements. With style-src 'unsafe-inline' there is nothing to do. With a nonce there is nothing to do either: the widget reads the nonce off the script that loaded it and stamps it on everything it injects. If the panel is unstyled and the console says nothing, this is where to look.

To allow no outside origin

Rewrite a path on your own domain to our API and pass it as apiBase. Requests, uploads and fonts then all come from your own origin.

js
// next.config.js
async rewrites() {
  return [{ source: "/_arcy/:path*", destination: "https://api.arcyai.com/:path*" }]
}
js
arcy.init("YOUR_ENVIRONMENT_TOKEN", { apiBase: "/_arcy" })

That closes everything on the npm path. The HTML snippet path still needs https://cdn.arcyai.com in script-src.

3. Verify installation says no traffic yet

arcy.init() never reached the selected environment. In order of likelihood:

  1. The page that calls arcy.init() has not been opened in a browser since you deployed it. Open it and reload while the check is watching.
  2. The token in init() belongs to another environment. Copy it again from Installation with the right environment chosen in the top row.
  3. A network error is swallowing the call. Look for the request init() makes in the browser's network tab.

4. Verify installation says the snippet carries another environment's token

Traffic is arriving, but from a snippet copied off a different environment's Installation page, for example the Development snippet in a Production deploy. Either switch the top row to the environment that is receiving, or replace the token in the live snippet with the one Installation shows.

5. Verify installation says a Site is not under a verified Domain

The token is right, but the request came from a Site under none of your workspace's verified Domains. The verdict names the refused Site. If it is yours, add and verify its Domain, or a name above it, on Domains. localhost never needs this.

6. Verify installation says identity verification is failing

Sessions arrive, but the userHash you pass does not verify against the environment's Secret. While Require identity verification is on, every affected session is served as anonymous, so attributes and personalization stop for those users.

Check your signing code against the recipe: HMAC-SHA256 of the exact string you pass to identify(), keyed with the Secret, hex encoded. The usual cause is a type mismatch: ids that are integers on the server and strings in the browser. A signature over 42 does not verify a call identifying "42". If you replaced the Secret, redeploy the new one; what replacing does is on API Keys.

7. A custom attribute is not showing up

Verify installation lists unknown attributes by name: a key that arrives without a declaration is dropped, never stored. Declare it on Attributes and send it again. Nothing is backfilled for traffic that arrived undeclared.

If the console shows this instead, the value's shape is the problem:

[arcy] identify() dropped "some_key": an attribute must be a string, number,
boolean, null, or an array of strings.

Flatten a nested object, a function or a Date to one of those shapes before passing it.

8. identify() or updateUser() seems to do nothing

[arcy] identify() needs a user id. Ignoring the call.
[arcy] identify() was called before init(). Ignoring.

The first means the user id was empty, undefined or not a string. The second is a race between your auth state and the script tag: call identify() where your session becomes available, after init() has run once on the page.

updateUser() before any identify() or identifyAnonymous() sends nothing, because there is no profile to update yet.

9. @types/arcy.js returns a 404

npm error 404 Not Found - GET https://registry.npmjs.org/@types%2farcy.js

That package does not exist. arcy.js ships its own TypeScript types, so remove the line and install only arcy.js. If your editor still reports missing types, run npm install arcy.js@latest.

10. The chat panel stopped opening on a tab left open for a long time

ARCY ships the chat panel, the flow engine and the picker as separate files with a version stamp in the name. A page open since before a release still holds the old names, so the first click asks for a file that is gone. arcy.js then asks the CDN for the current names and loads those; the visitor sees the panel open and nothing is logged.

Act only if the panel still does not open and you see:

[arcy] Failed to load https://cdn.arcyai.com/arcy.chat.<version>.js. The chat panel will not
open. If this page sets a Content-Security-Policy, it must allow scripts from
https://cdn.arcyai.com.

Then the retry failed too, and the cause is your policy (section 2), an ad blocker or corporate proxy on the visitor's machine, or a filtering gateway on your network. A reload always works. Do not build a reload prompt into your Product for this.

11. A web crawler source reads Failed

The toast says training failed and the row reads Failed. Point at Failed, or tab to it, for the reason; the toast shows the same sentence. If the reason names an address that could not be found, a refusal, a server error or a timeout, fix the site or the Starting URL and press Train again. If it says the pages could not be prepared, press Train again, and contact us if it happens again.

The most common reasons come from the site's own robots.txt. View log on the row shows Skipped: against every page it kept ARCY from. Read the file from your terminal:

curl https://yoursite.com/robots.txt

The reason says the site "blocks bots in its robots.txt file". The rules written for every bot are in force, either because the source has Follow robots.txt on or because its Site is under no verified Domain and is not your work email's domain (ARCYBot). The usual file blocks every crawler from every page:

User-agent: *
Disallow: /

This is what a staging or preview build ships. On Next.js the common cause is an "indexable" environment variable left unset in production, so the app/robots.ts file falls through to its block-everything branch. It also keeps search engines off your site.

If the Site is under a verified Domain, turn Follow robots.txt off on the source and press Train: nothing to deploy. Otherwise fix the file: make it allow crawling in production, or add a group for ARCY that leaves search engines blocked:

User-agent: ARCYBot
Allow: /

The reason says the site's robots.txt "has a rule for ARCYBot that blocks it". A group that names ARCYBot is always obeyed, whatever Follow robots.txt says. Change that group to Allow: / or delete it.

After either file change, deploy, run the curl again until the block is gone, then press Train.

www.yoursite.com and yoursite.com do not need separate treatment: one redirects to the other, and the crawler follows the redirect to the same robots.txt.

See also

On this page