API key types
There are three key types. They differ in what they can call and where they're allowed to run.
| Type | Where it runs | What it can call | Allowed domains | Shown in the dashboard |
|---|---|---|---|---|
| General | Anywhere | Everything below | Yes | Always |
| Public | Your pages, in the browser | The toolbox: GET /tools/toolbox/{key} and POST /tools/identify | Yes, recommended | Always |
| Private | Your server only | GET /tools/request/{requestId} and POST /data | No | Once, when created |
Results and data are checked per account, not per key, so a public key and a private (or general) key from the same account work together.
Why public and private keys
The toolbox runs in the respondent's browser, so the key that loads it is in your page source, and
anyone who views the page can copy it. With a general key, that copied key can also read results
and send data into your account. A result from GET /tools/request carries the respondent's
history across the whole co-op, including which panels have supplied them to other DQC customers.
A public key in the page can only load the toolbox. Reading and sending move to your server, with a private key that is refused the moment it shows up in a browser.
Which key for each integration
| Integration | Key |
|---|---|
| JavaScript routers and redirects, React with your own endpoint | Public key in the page, private key on your server. A general key for both still works. |
| Your own server sending POST /data | Private key. A general key still works. |
| Decipher | General key: the survey XML carries one key for the toolbox and the send. |
| Alchemer | Public or general key on page 1, general key on page 2: page 2 reads results in the browser. |
| Unicom, React without a server | General key: these read results or send data from the browser. |
For the integrations with a server part, Using a key pair is the step-by-step setup.
Public keys
A public key loads the toolbox and identifies respondents. It's meant to be visible in your page.
The toolbox's getIdentity and getIdentityRequestId calls use it from the toolbox URL:
<script type="module">
// Your public key (recommended) or your general key. Visible in the page by design.
const GENERAL_OR_PUBLIC_DQC_KEY = 'YOUR_GENERAL_OR_PUBLIC_KEY';
const { DQCToolBox } = await import(
`https://api.dqco-op.com/tools/toolbox/${GENERAL_OR_PUBLIC_DQC_KEY}`
);
</script>
Set its allowed domains, so only your survey sites can use it. If your server reads the results, server-only results also keep the scores off the page.
Errors
| Status | Message | Situation | What to do |
|---|---|---|---|
403 | Public keys cannot read results. Use a private key from your server. | A public key on GET /tools/request. | Read from your server with a private key, or use a general key if your integration reads in the browser. |
403 | Public keys cannot send data. Use a private key from your server. | A public key on POST /data. | Send from your server with a private key. |
403 | domain_not_allowed | Allowed domains are on and the request came from a site that isn't listed. | See Allowed domains. |
200 | Deactivated-Key special response | The key was deactivated. The toolbox keeps answering so live surveys don't break, but nothing is scored. | Reactivate the key, or switch to an active one. |
Allowed domains
Allowed domains limit where a public or general key can be used. When they're on, the toolbox, identify and browser result reads only answer requests from the sites you list. A key copied from your page then doesn't work on anyone else's site.
Set them up
- In the dashboard, go to Settings, API keys, and open the key's row menu. Choose Edit allowed domains.
- Choose Only these domains and add each domain your surveys run on, for example
survey.example.com. Tick Include subdomains to cover every subdomain of a domain. Pasting a full URL keeps only the domain. - If one of your servers uses the key, add the server's IPv4 address. Servers don't send the browser's origin, so without the address their requests are refused.
- Save. To turn the restriction off again, choose Open to all origins: your list is kept and applies again when you switch back.
localhost and private IP addresses can't be added.
Why they matter
A key in a web page can be copied by anyone who opens the page. Without allowed domains, someone could put your key on their own site and run the Quality Tools under your account. With allowed domains, their visitors' browsers report the real site, and the request is refused.
Allowed domains stop other websites, not scripts. A script outside a browser can claim to be one of your domains if it knows which ones you listed. If you think a key has been copied for misuse, deactivate it and create a new one.
How many domains per key
- List only the domains your surveys actually run on.
- Prefer exact domains. Use Include subdomains only when your surveys really run on several subdomains.
- Use one key per platform or group of related domains, rather than one key for everything. Each list stays short and easy to check, and if one key leaks you only replace that key.
Errors
| Status | Message | Situation | What to do |
|---|---|---|---|
403 | domain_not_allowed | The request came from a site that isn't listed, or from a server whose IP address isn't listed (a server sends no origin). | Add the domain or the server's IP address, or check you're using the right key on that site. |
The case that catches people out is a general key with allowed domains, used from your server: it is refused until you add the server's IP address.
Private keys
A private key reads results and sends data from your server: GET /tools/request/{requestId} and
POST /data, including a dedicated /data/... endpoint if DQC has issued you one.
-
Send it in the
Authorizationheader, on every request:Authorization: apikey YOUR_GENERAL_OR_PRIVATE_KEYapikey,ApiKeyandBearerare all accepted. The scheme word and the space are required. -
It's shown once, when you create it, and can't be shown again. The dashboard lists its last 4 characters so you can tell your private keys apart.
-
It has no allowed domains: a server has no origin to check.
-
It's refused on the toolbox and identify, in a request with an
Originheader (every browser request has one), and in the URL or the request body.
Refusing browser requests catches a private key put in a page by mistake. It doesn't protect a key that has leaked: treat a private key like a password.
Rotating a private key
- Routine rotation: create a new private key, deploy it to your server, then deactivate the old one.
- If a key has leaked: deactivate it first, then create and deploy a new one. You accept a short gap on your side, and whoever has the old key loses access immediately.
Errors
| Status | Message | Situation | What to do |
|---|---|---|---|
403 | Private keys cannot be used for the toolbox or identify. Use a public key in the browser. | A private key in the toolbox URL, or on POST /tools/identify. | Put a public key in the page. If the private key was in page code, deactivate it and create a new one. |
403 | Private keys must not be sent from a browser (Origin header present). | A private key on a request with an Origin header, Origin: null included. Every browser request has one, and so does a server that forwards a browser's headers. | Call from your server. If you proxy browser requests, remove Origin. |
403 | Private keys must be sent in the Authorization header, not in the URL. | A private key sent as ?apiKey= in the URL. | Move the key to the header. A key that was in a URL may be in proxy logs: consider rotating it. |
403 | Private keys must be sent in the Authorization header, not in the body. | A private key sent as "apiKey" in the JSON body of POST /data. | Move the key to the header. |
403 | Deactivated-Key | A private key that was deactivated in the dashboard, on any endpoint. | Reactivate it, or switch to an active key. Don't retry. |
What each case looks like on the wire. Only the first request is accepted:
# Accepted: key in the header, no Origin (a server, or curl)
curl "https://api.dqco-op.com/tools/request/$REQUEST_ID" \
-H "Authorization: apikey $GENERAL_OR_PRIVATE_DQC_KEY"
# Refused for a private key: the same call with an Origin header (what a browser adds)
GET /tools/request/1700000000000.Ab12Cd
Authorization: apikey YOUR_PRIVATE_KEY
Origin: https://survey.example.com
# Refused for a private key: the key in the URL
GET /tools/request/1700000000000.Ab12Cd?apiKey=YOUR_PRIVATE_KEY
# Refused for a private key: the key in the body
POST /data
Content-Type: application/json
{ "apiKey": "YOUR_PRIVATE_KEY", "requestId": "1700000000000.Ab12Cd", ... }
# Refused for a private key: the key in the toolbox URL
GET /tools/toolbox/YOUR_PRIVATE_KEY
The refused cases are written as plain requests on purpose: running them with a real key would put it in a URL or a log.
General keys
A general key is any key created before key types existed, or one you create on the General
tab. In the API, its keyType value is legacy. It works on every endpoint, from anywhere, as it always has: in the toolbox URL, on
GET /tools/request from the browser, on POST /data from a server, and as the ?apiKey= query
parameter. There is no deadline to stop using it.
It's the key to use for integrations that read results or send data from the browser, and for Decipher, whose survey XML carries one key for both jobs. See Which key for each integration.
If your integration has a server part, you can put a public key in your pages and keep your general key on the server. The key in the page then can't read or send anything. For the server, a private key is the better fit: see Using a key pair. General keys support allowed domains, the same way as public keys.
Errors
| Status | Message | Situation | What to do |
|---|---|---|---|
403 | domain_not_allowed | Allowed domains are on and the request came from an unlisted site or server. | See Allowed domains. |
200 | Deactivated-Key special response | A deactivated key on the toolbox, identify or a result read. Surveys keep running, but nothing is scored. | Reactivate the key, or switch to an active one. |
403 | Deactivated-Key | A deactivated key on POST /data. The record is not stored. | Reactivate the key, or switch to an active one. Don't retry. |
What a deactivated key returns on each endpoint is on Deactivated Key.
Errors for any key
| Status | Message | Situation | What to do |
|---|---|---|---|
401 | Missing Authorization header | No key was sent. | Add the Authorization header. |
401 | Api key not provided | The header has no key after the scheme word. | Use apikey YOUR_GENERAL_OR_PRIVATE_KEY, with the space. |
401 | Invalid ApiKey | The key doesn't exist, or belongs to another environment. | Check the key and the environment. |
401 | Authentication failed ... | We couldn't complete the key lookup. | Temporary. Retry with a back-off. |
403 | API keys cannot manage API keys. Use a dashboard session. | An API key was used on a key-management endpoint. | Manage keys in the dashboard. |
403 | API key has an unknown type | The key's type isn't one we recognise. The dashboard lists it with the type Unknown. | Deactivate it in the dashboard and create a new key, or contact us. |
Apart from Authentication failed, none of these is temporary: retrying won't help. The special
requestId values (Request-Blocked, Could not process, and the others) always get their
matching payload back, whatever key is sent, so they never produce a key-type 403.