Content Configuration GraphQL API
Create, update, and delete content from CI pipelines, scripts, or your own tools, without opening the Admin Console. Reads and writes use the same GraphQL endpoint.
Your GraphQL endpoint is https://tenant-name.is.here.io/here/api/graphql
POST /here/api/graphql
x-openfin-cloud-api-key: <your API key>
Content-Type: application/json
mutation {
createContent(input: {
contentType: WEB
name: "Trading Portal"
contentId: "trading-portal"
active: true
featured: false
urls: ["https://trading.example.com"]
access: { subjects: ["jane.doe@example.com"], primitives: ["all-users"] }
}) { uuid id name active }
}
Six fields are required: contentType, name, contentId, active, featured, and access. Desktop content also requires nativeSettings. The server sets defaults for the rest. See Defaults.
Authentication
Every request must carry one of these credentials:
| Credential | How to send it | Use it for |
|---|---|---|
| API key | x-openfin-cloud-api-key header | Unattended systems, such as CI pipelines or backend services. |
| Bearer token | Authorization: Bearer <token> header | Callers that already hold a HERE access token. |
| Session cookie | here-session cookie | Scripts or tools run by a signed-in user. |
For a pipeline or script that runs on its own, use an API key.
The credential must belong to an admin. What an admin can do depends on their admin type:
| Admin type | Read | Create, update, and delete |
|---|---|---|
| Read | Yes | No |
| Content Manager | Yes | Yes |
| Full Access | Yes | Yes |
A signed-in user who isn't an admin can't read or write content through this API.
A missing, expired, or invalid credential fails with UNAUTHENTICATED. A valid credential without the right admin type fails with FORBIDDEN. Error codes appear in extensions.code on each GraphQL error.
An API key has Full Access, so it can see and modify all items in the Admin Console. Store API keys in a secrets manager, give them only to the systems that need them, and rotate them regularly.
Identifiers
Every content item has two identifiers:
| Identifier | Notes |
|---|---|
uuid | Server-generated, stable, for example d48bf4cc-1b66-479e-a77d-59ab1e58557e. |
contentId | The human-readable ID you choose at creation, for example trading-portal. Unique within your organization. |
updateContent and deleteContent accept either one. If the value parses as a UUID, the API uses it as a UUID. Otherwise, the API looks it up by contentId. In scripts, use content IDs so you don't have to store UUIDs.
The content query works differently. See Read content.
createContent
createContent(input: CreateContentInput!): ContentBase!
Set contentType to WEB or DESKTOP. The type decides which other fields CreateContentInput accepts. For DESKTOP, nativeSettings is required, along with all three of its fields: desktopPath, desktopArgs, and withSnap. Every other field is optional.
Minimal web application
mutation CreateContent($input: CreateContentInput!) {
createContent(input: $input) {
uuid
id
name
type
active
}
}
{
"input": {
"contentType": "WEB",
"name": "Trading Portal",
"contentId": "trading-portal",
"active": true,
"featured": false,
"urls": ["https://trading.example.com"],
"access": {
"subjects": ["jane.doe@example.com"],
"primitives": ["all-users"]
}
}
}
Web application, every field set
{
"input": {
"contentType": "WEB",
"name": "Trading Portal",
"contentId": "trading-portal",
"active": true,
"featured": true,
"icon": "https://cdn.example.com/trading-icon.png",
"urls": ["https://trading.example.com", "https://trading-staging.example.com"],
"hereApiAccess": true,
"allowDuplication": false,
"allowOpenWithDefaultBrowser": false,
"useAIContext": true,
"enableSimpleWindow": false,
"environmentAvailability": {
"enableHereEB": true,
"enableHereZero": true,
"enableHereMobile": false
},
"viewSettings": {
"navigationControls": true,
"reloadControl": true
},
"dataLossPreventionSettings": {
"copyBehavior": "protect",
"pasteBehavior": "non-protected-content",
"screenCaptureBehavior": "block",
"printBehavior": "block"
},
"redirects": [],
"access": {
"subjects": ["jane.doe@example.com"],
"primitives": ["all-users"]
}
}
}
Desktop application
{
"input": {
"contentType": "DESKTOP",
"name": "Bloomberg Terminal",
"contentId": "bloomberg-terminal",
"active": true,
"featured": false,
"nativeSettings": {
"desktopPath": "C:\\Bloomberg\\blp.exe",
"desktopArgs": ["--workspace"],
"withSnap": true
},
"access": {
"subjects": [],
"primitives": ["all-users"]
}
}
}
Web application with FDC3 intents
{
"input": {
"contentType": "WEB",
"name": "Chart Viewer",
"contentId": "chart-viewer",
"active": true,
"featured": false,
"urls": ["https://charts.example.com"],
"interop": {
"intents": {
"listensFor": {
"ViewChart": {
"displayName": "View Chart",
"contexts": ["fdc3.instrument"]
}
}
}
},
"access": {
"subjects": [],
"primitives": ["all-users"]
}
}
}
Field reference
Common to both content types
| Field | Type | Notes |
|---|---|---|
contentType * | WEB | DESKTOP | Selects which type-specific fields are valid. |
name * | String | Display name that users see. |
contentId * | String | Your stable identifier. 1–255 characters: letters, numbers, and hyphens only. Must be unique in your organization. A duplicate fails with CONFLICT. |
active * | Boolean | Whether the content is available to users. |
featured * | Boolean | Promotes the item in the storefront. |
access * | ContentAccessInput | Who can launch it. See Access control. |
icon | String | Absolute URL to an icon image. |
customLabel | String | Optional secondary label. |
* required on create
Web content
| Field | Type | Notes |
|---|---|---|
urls | [String!] | One or more launch URLs. The first is the primary url on read. |
hereApiAccess | Boolean | Grants the page access to the HERE JavaScript API. |
allowDuplication | Boolean | Lets users open more than one window of the app. |
allowOpenWithDefaultBrowser | Boolean | Adds an "open in browser" action. |
useAIContext | Boolean | Makes the app's content available to AI features. |
enableSimpleWindow | Boolean | Renders in a chromeless window. |
environmentAvailability | object | enableHereEB, enableHereZero, enableHereMobile. |
viewSettings | object | navigationControls, reloadControl. |
dataLossPreventionSettings | object | copyBehavior, pasteBehavior, screenCaptureBehavior, printBehavior. |
redirects | [ContentRedirectInput!] | Domain redirect rules. |
interop | object | FDC3 intents the app listens for. |
Desktop content
| Field | Type | Notes |
|---|---|---|
nativeSettings.desktopPath * | String | Path to the executable on the user's machine. Escape backslashes in JSON. |
nativeSettings.desktopArgs * | [String!] | Launch arguments. |
nativeSettings.withSnap * | Boolean | Participates in window snapping. |
* required on create
Defaults
When you omit an optional field, the server uses the same default as the Admin Console: permissive data loss prevention, availability in HERE Enterprise Browser only, and standard view settings. Defaults can change between releases, so set any field whose value matters to you.
Access control
input ContentAccessInput {
subjects: [String!]! # individual users
primitives: [String!]! # groups
}
Both lists accept names or UUIDs, and one list can mix them. If a value parses as a UUID, the API uses it as a UUID. Otherwise, the API looks it up by username in subjects or by group name in primitives.
If a name doesn't resolve, the mutation fails with NOT_FOUND and the content doesn't change.
This controls which users and groups can launch a content item. To change which groups a user belongs to, use the User Group Management API.
"access": {
"subjects": ["jane.doe@example.com", "sam.patel@example.com"],
"primitives": ["traders-group"]
}
createContents
createContents(inputs: [CreateContentInput!]!): BulkCreateContentResult!
Creates several items in one request, and returns them in created. If any item fails, the whole request fails.
mutation CreateContents($inputs: [CreateContentInput!]!) {
createContents(inputs: $inputs) {
created { uuid id name }
}
}
{
"inputs": [
{
"contentType": "WEB",
"name": "Trading Portal",
"contentId": "trading-portal",
"active": true,
"featured": false,
"urls": ["https://trading.example.com"],
"access": { "subjects": [], "primitives": ["all-users"] }
},
{
"contentType": "WEB",
"name": "Chart Viewer",
"contentId": "chart-viewer",
"active": true,
"featured": false,
"urls": ["https://charts.example.com"],
"access": { "subjects": [], "primitives": ["traders-group"] }
}
]
}
updateContent
updateContent(identifier: ID!, input: UpdateContentInput!): ContentBase!
UpdateContentInput has the same fields as CreateContentInput, but they're all optional. Send only the fields you want to change. Everything else stays as it is. If you omit contentType, the item keeps its current type.
How access updates
| Value | Effect |
|---|---|
omitted or null | Leave the current access list untouched. |
{ subjects: [...], primitives: [...] } | Replace the access list with exactly these entries. |
{ subjects: [], primitives: [] } | Remove all access. Users no longer launch the item. |
To add or remove a single user or group, read the current access list, change it, and send the full list back.
Example
mutation UpdateContent($identifier: ID!, $input: UpdateContentInput!) {
updateContent(identifier: $identifier, input: $input) {
uuid
name
active
... on WebContent { urls }
}
}
{
"identifier": "trading-portal",
"input": {
"name": "Trading Portal (v2)",
"featured": true,
"urls": ["https://trading.example.com", "https://trading-v2.example.com"],
"access": null
}
}
deleteContent
deleteContent(identifier: ID!): Boolean!
mutation { deleteContent(identifier: "trading-portal") }
{ "data": { "deleteContent": true } }
Deletion is permanent. It also removes the item from users' saved docks, without notifying them. To hide an item but keep its record, set active to false.
Read content
content reads one item. Pass a content ID as id, or a UUID as uuid:
query { content(id: "trading-portal") { uuid name } }
query { content(uuid: "d48bf4cc-1b66-479e-a77d-59ab1e58557e") { id name } }
Unlike updateContent and deleteContent, content doesn't detect UUIDs. content(id:) with a UUID doesn't find the item.
One item
query {
content(id: "trading-portal") {
uuid
name
type
active
access { subjects primitives permissions }
... on WebContent {
urls
hereApiAccess
dataLossPreventionSettings {
copyBehavior pasteBehavior screenCaptureBehavior printBehavior
}
}
}
}
On read, access returns UUIDs, even if you wrote names.
Paginated list
query ListContents {
contents(first: 50) {
totalCount
pageInfo { hasNextPage endCursor }
edges {
node {
uuid
id
name
type
active
... on WebContent { url urls }
... on DesktopContent { desktopPath desktopArgs withSnap }
}
}
}
}
Full snapshot with a fragment
Use this fragment to export items, compare them, or read an item before you update it.
fragment ContentFields on ContentBase {
uuid
id
name
type
active
customLabel
featured
icon
createdAt
access { subjects primitives permissions }
... on WebContent {
url
urls
hereApiAccess
allowDuplication
allowOpenWithDefaultBrowser
useAIContext
enableSimpleWindow
environmentAvailability { enableHereEB enableHereZero enableHereMobile }
viewSettings { navigationControls reloadControl }
dataLossPreventionSettings {
copyBehavior pasteBehavior screenCaptureBehavior printBehavior
}
redirects { id name description redirectDetails { domains name icon } }
}
... on DesktopContent {
desktopPath
desktopArgs
withSnap
}
}
query {
contents(first: 50) {
totalCount
pageInfo { hasNextPage endCursor }
edges { node { ...ContentFields } }
}
}
Behavior to plan for
- The last write wins. If a script and an administrator edit the same item at the same time, one change silently overwrites the other. Avoid running automated and manual edits on the same content at once.
- Every write is audited. Each create, update, and delete produces an audit event. A pipeline that creates hundreds of items in a tight loop produces a matching burst of audit events, so pace large migrations.
- Deleting an item removes it from users' docks. See deleteContent.
- Names cost a lookup. When you create many items, resolve names once and pass the UUIDs in
access. - List fields replace the whole list. Sending
urls,access, orredirectsreplaces the existing list. Other fields update individually.