Apps API
Every Apps action (14) and data model (1): endpoints, scopes, input, preconditions, events and examples.
The apps and services you connected, and the updates sent to them.
Module code apps · 14 actions · 1 data models · 12 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: apps (everything), apps:read (read only) or apps:<capability>.
Resources
| Resource | Table | List and read | POST | PATCH | DELETE |
|---|---|---|---|---|---|
app_connection_choices | app_connection_choice | /api/v1/app_connection_choices |
Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.
Actions
| Action | Needs | What it does |
|---|---|---|
apps.add_connection | apps:read | Add a connection |
apps.connect | apps:manage | Record a connection |
apps.connect_whatsapp | apps:read | Connect WhatsApp Business |
apps.disconnect | apps:delete | Disconnect |
apps.record_failure | apps:handle | Record a connection failure |
apps.record_healthy | apps:handle | Record a passed connection test |
apps.record_poll | apps:handle | Record a poll of a connected app |
apps.register_installation | apps:manage | Register an installation |
apps.rotate_secret | apps:handle | Refresh a connection’s token |
apps.set_connection_choice | apps:read | Choose which account to use |
apps.set_connection_owner | apps:read | Change whose a connection is |
apps.set_paused | apps:manage | Pause or resume a connection |
apps.store_secret | apps:manage | Store a secret |
apps.update_connection | apps:manage | Change a connection |
apps.add_connection
Add a connection — One more account of an app — yours or the company’s — with its key sealed in the vault.
Needs apps:read · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/actions/apps.add_connectionInput
| Field | Type | Required | Notes |
|---|---|---|---|
provider | string | yes | App · max 64 |
label | string | yes | Name · max 160 |
kind | string (enum) | yes | Kind · Allowed values: oauth2, api_key, basic |
value | string | Key · max 16000 | |
secretRef | string | Where the secret is kept · max 200 | |
accountRef | string | Account (e.g. info@…) · max 200 | |
owner | string (enum) | Whose · Allowed values: me, workspace | |
share | string (enum) | Who may use it · Allowed values: me, team, workspace | |
teamId | uuid | Team |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
empty_label | A connection needs a name. |
bad_provider | An app is a lowercase slug. |
not_manager | A company connection is added by an owner or manager. |
team_required | Sharing with a team names the team. |
no_secret | A connection points at a key or a signed-in account. |
label_taken | Two live connections of one app cannot share a name. |
Events it publishes: apps.connected
What it writes: secret (insert) — Seals the key, when one is given; credential (insert) — Writes the connection with its owner and share
curl -X POST https://app.getera.io/api/v1/actions/apps.add_connection \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"provider":"App","label":"Name","kind":"oauth2"}'{
"ok": true,
"result": "…",
"events": [
"apps.connected"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.add_connection", {
provider: "App",
label: "Name",
kind: "oauth2"
})era apps add_connection \
--provider "App" \
--label "Name" \
--kind "oauth2"{
"name": "era__apps__add_connection",
"arguments": {
"provider": "App",
"label": "Name",
"kind": "oauth2"
}
}Errors: 401 no or expired credential · 403 the credential does not hold apps:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 empty_label, bad_provider, not_manager, team_required, no_secret, label_taken · 429 slow down
apps.connect
Record a connection — Writes the one row that says this workspace is connected to something.
Needs apps:manage · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/actions/apps.connectInput
| Field | Type | Required | Notes |
|---|---|---|---|
provider | string | yes | Provider · max 64 |
label | string | yes | Name · max 160 |
kind | string (enum) | yes | Kind · Allowed values: oauth2, api_key, basic, mcp, signing |
secretRef | string | yes | Where the secret is kept · The name of the stored secret — never the secret itself. Nothing here is ever a token. · max 200 |
accountRef | string | Account at the provider · max 200 | |
scopes | object / array | Scopes granted | |
expiresAt | date-time | Expires |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
empty_label | A connection needs a name. |
bad_provider | A provider is a lowercase slug. |
bad_scopes | Scopes are a list of strings. |
label_taken | Two live connections to one provider cannot share a name. |
Events it publishes: apps.connected
What it writes: credential (insert) — Writes the connection. Never the secret
curl -X POST https://app.getera.io/api/v1/actions/apps.connect \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"provider":"Provider","label":"Name","kind":"oauth2","secretRef":"Where the secret is kept"}'{
"ok": true,
"result": "…",
"events": [
"apps.connected"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.connect", {
provider: "Provider",
label: "Name",
kind: "oauth2",
secretRef: "Where the secret is kept"
})era apps connect \
--provider "Provider" \
--label "Name" \
--kind "oauth2" \
--secret-ref "Where the secret is kept"{
"name": "era__apps__connect",
"arguments": {
"provider": "Provider",
"label": "Name",
"kind": "oauth2",
"secretRef": "Where the secret is kept"
}
}Errors: 401 no or expired credential · 403 the credential does not hold apps:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 empty_label, bad_provider, bad_scopes, label_taken · 429 slow down
apps.connect_whatsapp
Connect WhatsApp Business — Records a WhatsApp Business number (from Meta’s sign-up) as your or the company’s connection, with its token sealed in the vault.
Needs apps:read · reaches outside (sends, publishes or charges)
https://app.getera.io/api/v1/actions/apps.connect_whatsappInput
| Field | Type | Required | Notes |
|---|---|---|---|
phoneNumberId | string | yes | Phone number id · max 40 |
wabaId | string | yes | WhatsApp Business Account id · max 40 |
businessId | string | Business portfolio id · max 40 | |
displayNumber | string | yes | Number · max 40 |
verifiedName | string | Display name · max 200 | |
token | string | yes | Business token · max 4000 |
pin | string | Two-step PIN · max 6 | |
via | string (enum) | Connected by · Allowed values: embedded_signup, manual | |
owner | string (enum) | Whose · Allowed values: me, workspace | |
share | string (enum) | Who may use it · Allowed values: me, team, workspace | |
teamId | uuid | Team |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
bad_whatsapp_ids | The ids are Meta’s numeric ids and the number is a full international number. |
not_manager | The company’s WhatsApp is added by an owner or manager. |
team_required | Sharing with a team names the team. |
whatsapp_number_taken | A number that is a colleague’s personal connection (or the company’s, for a non-manager) is not taken over. |
Events it publishes: apps.connected, apps.connection_updated
What it writes: secret (upsert) — Seals the business token (and the PIN), rotating in place on a reconnect; credential (upsert) — One connection per number, with its owner and Meta ids
curl -X POST https://app.getera.io/api/v1/actions/apps.connect_whatsapp \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"phoneNumberId":"Phone number id","wabaId":"WhatsApp Business Account id","displayNumber":"Number","token":"Business token"}'{
"ok": true,
"result": "…",
"events": [
"apps.connected",
"apps.connection_updated"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.connect_whatsapp", {
phoneNumberId: "Phone number id",
wabaId: "WhatsApp Business Account id",
displayNumber: "Number",
token: "Business token"
})era apps connect_whatsapp \
--phone-number-id "Phone number id" \
--waba-id "WhatsApp Business Account id" \
--display-number "Number" \
--token "Business token"{
"name": "era__apps__connect_whatsapp",
"arguments": {
"phoneNumberId": "Phone number id",
"wabaId": "WhatsApp Business Account id",
"displayNumber": "Number",
"token": "Business token"
}
}Errors: 401 no or expired credential · 403 the credential does not hold apps:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 bad_whatsapp_ids, not_manager, team_required, whatsapp_number_taken · 429 slow down
apps.disconnect
Disconnect — Hides the connection and marks it revoked. Anything signing with it starts failing.
Needs apps:delete · removes or cannot be undone · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/actions/apps.disconnectInput
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Connection · id of credential |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The connection must exist in this workspace. |
Events it publishes: apps.disconnected
What it writes: credential (update) — Writes the tombstone, through sql/057’s door
curl -X POST https://app.getera.io/api/v1/actions/apps.disconnect \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"id":"CREDENTIAL_ID"}'{
"ok": true,
"result": "…",
"events": [
"apps.disconnected"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.disconnect", {
id: "CREDENTIAL_ID"
})era apps disconnect \
--id "CREDENTIAL_ID"{
"name": "era__apps__disconnect",
"arguments": {
"id": "CREDENTIAL_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold apps:delete · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down
apps.record_failure
Record a connection failure — Keeps the provider’s own complaint on the connection, verbatim.
Needs apps:handle · changes data
https://app.getera.io/api/v1/actions/apps.record_failureInput
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Connection · id of credential |
message | string | yes | What the provider said · max 2000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The connection must exist in this workspace. |
Events it publishes: apps.connection_failed
What it writes: credential (update) — Marks it in error and keeps the message
curl -X POST https://app.getera.io/api/v1/actions/apps.record_failure \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"id":"CREDENTIAL_ID","message":"What the provider said"}'{
"ok": true,
"result": "…",
"events": [
"apps.connection_failed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.record_failure", {
id: "CREDENTIAL_ID",
message: "What the provider said"
})era apps record_failure \
--id "CREDENTIAL_ID" \
--message "What the provider said"{
"name": "era__apps__record_failure",
"arguments": {
"id": "CREDENTIAL_ID",
"message": "What the provider said"
}
}Errors: 401 no or expired credential · 403 the credential does not hold apps:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down
apps.record_healthy
Record a passed connection test — Clears the error on a connection whose test passed, and stamps when.
Needs apps:handle · changes data
https://app.getera.io/api/v1/actions/apps.record_healthyInput
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Connection · id of credential |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The connection must exist in this workspace. |
Events it publishes: apps.connection_healthy
What it writes: credential (update) — Back to active, error cleared, test time stamped
curl -X POST https://app.getera.io/api/v1/actions/apps.record_healthy \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"id":"CREDENTIAL_ID"}'{
"ok": true,
"result": "…",
"events": [
"apps.connection_healthy"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.record_healthy", {
id: "CREDENTIAL_ID"
})era apps record_healthy \
--id "CREDENTIAL_ID"{
"name": "era__apps__record_healthy",
"arguments": {
"id": "CREDENTIAL_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold apps:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down
apps.record_poll
Record a poll of a connected app — Moves the checkpoint of one feed of an installation and admits each new item once, as an apps.app_event.
Needs apps:handle · changes data
https://app.getera.io/api/v1/actions/apps.record_pollInput
| Field | Type | Required | Notes |
|---|---|---|---|
credentialId | uuid | yes | Connection · id of credential |
feed | string | yes | Feed · max 64 |
cursor | string | Checkpoint · max 2000 | |
items | object / array | New items | |
error | string | What the provider said · max 2000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The connection must exist in this workspace. |
invalid_feed | A feed name is lowercase letters, digits and underscores. |
Events it publishes: apps.app_event, apps.poll_failed, apps.connection_failed
What it writes: app_poll_state (upsert) — Moves the checkpoint, or counts the failure; app_poll_seen (insert) — Admits each item id once per feed; credential (update) — Stamps last use; goes red after three failures in a row
curl -X POST https://app.getera.io/api/v1/actions/apps.record_poll \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"credentialId":"CREDENTIAL_ID","feed":"Feed"}'{
"ok": true,
"result": "…",
"events": [
"apps.app_event",
"apps.poll_failed",
"apps.connection_failed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.record_poll", {
credentialId: "CREDENTIAL_ID",
feed: "Feed"
})era apps record_poll \
--credential-id "CREDENTIAL_ID" \
--feed "Feed"{
"name": "era__apps__record_poll",
"arguments": {
"credentialId": "CREDENTIAL_ID",
"feed": "Feed"
}
}Errors: 401 no or expired credential · 403 the credential does not hold apps:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, invalid_feed · 429 slow down
apps.register_installation
Register an installation — Adopts a connector, OAuth account or MCP server the engine connected as this workspace’s installation record, so it can be tested, paused and uninstalled in one place.
Needs apps:manage · changes data
https://app.getera.io/api/v1/actions/apps.register_installationInput
| Field | Type | Required | Notes |
|---|---|---|---|
source | string (enum) | yes | Where it was connected · Allowed values: connector, oauth, mcp_server, engine_credential |
sourceRef | string | yes | Engine row · max 200 |
provider | string | yes | Provider · max 64 |
label | string | yes | Name · max 160 |
kind | string (enum) | yes | Kind · Allowed values: oauth2, api_key, basic, mcp, signing |
accountRef | string | Account at the provider · max 200 | |
scopes | object / array | Scopes granted |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
empty_label | A connection needs a name. |
bad_provider | A provider is a lowercase slug. |
bad_scopes | Scopes are a list of strings. |
Events it publishes: apps.connected
What it writes: credential (insert) — Writes the installation row, or returns the existing one
curl -X POST https://app.getera.io/api/v1/actions/apps.register_installation \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"source":"connector","sourceRef":"Engine row","provider":"Provider","label":"Name","kind":"oauth2"}'{
"ok": true,
"result": "…",
"events": [
"apps.connected"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.register_installation", {
source: "connector",
sourceRef: "Engine row",
provider: "Provider",
label: "Name",
kind: "oauth2"
})era apps register_installation \
--source "connector" \
--source-ref "Engine row" \
--provider "Provider" \
--label "Name" \
--kind "oauth2"{
"name": "era__apps__register_installation",
"arguments": {
"source": "connector",
"sourceRef": "Engine row",
"provider": "Provider",
"label": "Name",
"kind": "oauth2"
}
}Errors: 401 no or expired credential · 403 the credential does not hold apps:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 empty_label, bad_provider, bad_scopes · 429 slow down
apps.rotate_secret
Refresh a connection’s token — Re-seals a refreshed OAuth token over the same vault entry — the worker’s verb, narrower than store_secret.
Needs apps:handle · changes data
https://app.getera.io/api/v1/actions/apps.rotate_secretInput
| Field | Type | Required | Notes |
|---|---|---|---|
credentialId | uuid | yes | Connection · id of credential |
value | string | yes | Secret · max 16000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The connection must exist. |
no_vault_secret | Only a secret already in the vault is rotated. |
Events it publishes: apps.connection_updated
What it writes: secret (update) — Rotates the sealed value in place
curl -X POST https://app.getera.io/api/v1/actions/apps.rotate_secret \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"credentialId":"CREDENTIAL_ID","value":"Secret"}'{
"ok": true,
"result": "…",
"events": [
"apps.connection_updated"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.rotate_secret", {
credentialId: "CREDENTIAL_ID",
value: "Secret"
})era apps rotate_secret \
--credential-id "CREDENTIAL_ID" \
--value "Secret"{
"name": "era__apps__rotate_secret",
"arguments": {
"credentialId": "CREDENTIAL_ID",
"value": "Secret"
}
}Errors: 401 no or expired credential · 403 the credential does not hold apps:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, no_vault_secret · 429 slow down
apps.set_connection_choice
Choose which account to use — What an Era, a workflow or your chats use for an app: your own (Run as), the company’s, or one by name.
Needs apps:read · changes data
https://app.getera.io/api/v1/app_connection_choices/actions/set_connection_choice- Canonical address:
POST /api/v1/actions/apps.set_connection_choice
Input
| Field | Type | Required | Notes |
|---|---|---|---|
subjectType | string (enum) | yes | For · Allowed values: chat, agent, workflow, voice_agent |
subjectId | string | Which one · max 200 | |
family | string | yes | App · max 64 |
pick | string (enum) | yes | Use · Allowed values: mine, workspace, connection |
connectionId | uuid | Connection · id of credential |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
subject_required | An Era, workflow or voice agent is named. |
connection_required | A named pick names the connection. |
connection_not_found | The connection must exist in this workspace. |
provider_mismatch | The connection must be of the same app. |
connection_not_usable | Only a connection the chooser may use can be picked. |
Events it publishes: apps.connection_choice_set
What it writes: app_connection_choice (upsert) — One row per thing and app
curl -X POST https://app.getera.io/api/v1/app_connection_choices/actions/set_connection_choice \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"subjectType":"chat","family":"App","pick":"mine"}'{
"ok": true,
"result": "…",
"events": [
"apps.connection_choice_set"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appConnectionChoices.setConnectionChoice({
subjectType: "chat",
family: "App",
pick: "mine"
})era app_connection_choices set_connection_choice \
--subject-type "chat" \
--family "App" \
--pick "mine"{
"name": "era__apps__set_connection_choice",
"arguments": {
"subjectType": "chat",
"family": "App",
"pick": "mine"
}
}Errors: 401 no or expired credential · 403 the credential does not hold apps:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 subject_required, connection_required, connection_not_found, provider_mismatch, connection_not_usable · 429 slow down
apps.set_connection_owner
Change whose a connection is — Personal or company, and who else may use it. Only a personal connection’s owner may change it.
Needs apps:read · changes data
https://app.getera.io/api/v1/actions/apps.set_connection_ownerInput
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Connection · id of credential |
owner | string (enum) | yes | Whose · Allowed values: me, workspace |
share | string (enum) | Who may use it · Allowed values: me, team, workspace | |
teamId | uuid | Team |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The connection must exist in this workspace. |
not_owner | Only the owner of a personal connection changes it. |
not_manager | Company connections are changed by an owner or manager. |
workspace_owned | A company connection never becomes someone’s personal one. |
team_required | Sharing with a team names the team. |
Events it publishes: apps.connection_updated
What it writes: credential (update) — Sets owner_user_id, share_scope and share_team_id
curl -X POST https://app.getera.io/api/v1/actions/apps.set_connection_owner \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"id":"CREDENTIAL_ID","owner":"me"}'{
"ok": true,
"result": "…",
"events": [
"apps.connection_updated"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.set_connection_owner", {
id: "CREDENTIAL_ID",
owner: "me"
})era apps set_connection_owner \
--id "CREDENTIAL_ID" \
--owner "me"{
"name": "era__apps__set_connection_owner",
"arguments": {
"id": "CREDENTIAL_ID",
"owner": "me"
}
}Errors: 401 no or expired credential · 403 the credential does not hold apps:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, not_owner, not_manager, workspace_owned, team_required · 429 slow down
apps.set_paused
Pause or resume a connection — A paused connection keeps its secret and history; chats, agents and workflows skip it.
Needs apps:manage · changes data
https://app.getera.io/api/v1/actions/apps.set_pausedInput
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Connection · id of credential |
paused | boolean | yes | Paused |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The connection must exist in this workspace. |
not_pausable | A revoked or expired connection is reconnected, not paused. |
no_secret | A connection that points at no secret cannot be resumed. |
Events it publishes: apps.paused, apps.resumed
What it writes: credential (update) — Flips the status between paused and active
curl -X POST https://app.getera.io/api/v1/actions/apps.set_paused \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"id":"CREDENTIAL_ID","paused":true}'{
"ok": true,
"result": "…",
"events": [
"apps.paused",
"apps.resumed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.set_paused", {
id: "CREDENTIAL_ID",
paused: true
})era apps set_paused \
--id "CREDENTIAL_ID" \
--paused true{
"name": "era__apps__set_paused",
"arguments": {
"id": "CREDENTIAL_ID",
"paused": true
}
}Errors: 401 no or expired credential · 403 the credential does not hold apps:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, not_pausable, no_secret · 429 slow down
apps.store_secret
Store a secret — Seals a key, token or password in the vault. It can be replaced, never read back.
Needs apps:manage · changes data
https://app.getera.io/api/v1/actions/apps.store_secretInput
| Field | Type | Required | Notes |
|---|---|---|---|
credentialId | uuid | Connection · id of credential | |
value | string | yes | Secret · max 16000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The connection must exist in this workspace. |
Events it publishes: apps.connection_updated
What it writes: secret (upsert) — Seals the value; a second call rotates it in place; credential (update) — Points the connection at the vault secret
curl -X POST https://app.getera.io/api/v1/actions/apps.store_secret \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"value":"Secret"}'{
"ok": true,
"result": "…",
"events": [
"apps.connection_updated"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.store_secret", {
value: "Secret"
})era apps store_secret \
--value "Secret"{
"name": "era__apps__store_secret",
"arguments": {
"value": "Secret"
}
}Errors: 401 no or expired credential · 403 the credential does not hold apps:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down
apps.update_connection
Change a connection — Renames it, repoints it at a different stored secret, or marks it expired.
Needs apps:manage · changes data
https://app.getera.io/api/v1/actions/apps.update_connectionInput
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Connection · id of credential |
label | string | Name · max 160 | |
secretRef | string | Where the secret is kept · The name of the stored secret — never the secret itself. Nothing here is ever a token. · max 200 | |
accountRef | string | Account at the provider · max 200 | |
scopes | object / array | Scopes granted | |
expiresAt | date-time | Expires | |
status | string (enum) | Status · Allowed values: active, expired, revoked, error |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The connection must exist in this workspace. |
empty_label | A connection needs a name. |
bad_scopes | Scopes are a list of strings. |
Events it publishes: apps.connection_updated
What it writes: credential (update) — Rewrites the connection
curl -X POST https://app.getera.io/api/v1/actions/apps.update_connection \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"id":"CREDENTIAL_ID"}'{
"ok": true,
"result": "…",
"events": [
"apps.connection_updated"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.update_connection", {
id: "CREDENTIAL_ID"
})era apps update_connection \
--id "CREDENTIAL_ID"{
"name": "era__apps__update_connection",
"arguments": {
"id": "CREDENTIAL_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold apps:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, empty_label, bad_scopes · 429 slow down
Data models
AppConnectionChoice
Table app_connection_choice · Resource app_connection_choices
GET /api/v1/app_connection_choicesGET /api/v1/app_connection_choices/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
subject_type | string · text | chat, agent, workflow, voice_agent | ||
subject_id | string · text | |||
family | string · text | |||
pick | string · text | mine, workspace, connection | ||
connection_id | string (uuid) · uuid | yes | credential | |
created_by | string (uuid) · uuid | yes | app_user | |
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz |
Primary key: id
Unique: workspace_id, subject_type, subject_id, family
Checks:
CHECK ((family ~ '^[a-z][a-z0-9_]{1,63}$'::text))
Events
Workflows and webhooks react to these events.
| Event | What happens | Payload |
|---|---|---|
apps.app_event | A connected app reported something new (polled, admitted once). | id, provider, feed, external_id, occurred_at, summary, item |
apps.connected | This workspace connected something. | id, provider, kind |
apps.connection_choice_set | An Era, workflow or chat was told which account of an app to use. | subject_type, subject_id, family, pick |
apps.connection_failed | A provider refused a connection. | id, provider |
apps.connection_healthy | A connection test passed. | id, provider |
apps.connection_updated | A connection was renamed or repointed. | id, provider |
apps.delivered | An outbound webhook was delivered. | webhook_id, event_name |
apps.delivery_failed | An outbound webhook could not be delivered. | webhook_id, event_name, status |
apps.disconnected | A connection was revoked. | id, provider |
apps.paused | A person paused a connection; the runtime skips it. | id, provider |
apps.poll_failed | A connected app refused a poll. | id, provider, feed, failures, message |
apps.resumed | A paused connection was switched back on. | id, provider |