ReferenceReference by module

Tags API

Every Tags action (6) and data model (2): endpoints, scopes, input, preconditions, events and examples.

One shared set of tags for every record, in your own colours.

Module code tags · 6 actions · 2 data models · 6 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: tags (everything), tags:read (read only) or tags:<capability>.

Resources

ResourceTableList and readPOSTPATCHDELETE
tag_linkstag_link/api/v1/tag_links
tagstag/api/v1/tagstags.createtags.updatetags.delete

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

ActionNeedsWhat it does
tags.attachtags:readTag a record
tags.createtags:manageCreate a tag
tags.deletetags:manageDelete a tag
tags.detachtags:readRemove a tag from a record
tags.mergetags:manageMerge two tags
tags.updatetags:manageRename or recolour a tag

tags.attach

Tag a record — Puts an existing tag on a record. Needs permission to edit that record.

Needs tags:read · changes data

POSThttps://app.getera.io/api/v1/tag_links/actions/attach
API key or OAuth token · tags:read
  • Canonical address: POST /api/v1/actions/tags.attach

Input

FieldTypeRequiredNotes
tagIduuidyesTag · id of tag
recordTypestringyesRecord type · max 60
recordIduuidyesRecord

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
unknown_record_typeThe record type must be one a module owns.
cannot_write_recordTagging a record needs write on that record’s module.
tag_not_foundThe tag must exist in this workspace.
record_not_foundThe record must exist.

Events it publishes: tags.attached

What it writes: tag_link (insert) — Attaches the tag

curl -X POST https://app.getera.io/api/v1/tag_links/actions/attach \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"tagId":"TAG_ID","recordType":"Record type","recordId":"RECORD_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "tags.attached"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.tagLinks.attach({
  tagId: "TAG_ID",
  recordType: "Record type",
  recordId: "RECORD_ID"
})
era tag_links attach \
  --tag-id "TAG_ID" \
  --record-type "Record type" \
  --record-id "RECORD_ID"
tools/call
{
  "name": "era__tags__attach",
  "arguments": {
    "tagId": "TAG_ID",
    "recordType": "Record type",
    "recordId": "RECORD_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold tags:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 unknown_record_type, cannot_write_record, tag_not_found, record_not_found · 429 slow down

tags.create

Create a tag — Adds an entry to the workspace vocabulary every module shares.

Needs tags:manage · changes data

POSThttps://app.getera.io/api/v1/tags/actions/create
API key or OAuth token · tags:manage
  • REST alias: POST /api/v1/tags
  • Canonical address: POST /api/v1/actions/tags.create

Input

FieldTypeRequiredNotes
namestringyesName · max 120
colorstringColour · Six hex digits after a hash. Left empty, the tag takes a stable colour derived from its id. · max 7
descriptionstringDescription · max 2000
isPinnedbooleanPin to filters

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
empty_nameA tag needs a name that reduces to a key.
slug_takenTwo tags may not reduce to the same key.
bad_colourA colour must be six hexadecimal digits after a hash.

Events it publishes: tags.created

What it writes: tag (insert) — Writes the vocabulary entry

curl -X POST https://app.getera.io/api/v1/tags/actions/create \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Name"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "tags.created"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.tags.create({
  name: "Name"
})
era tags create \
  --name "Name"
tools/call
{
  "name": "era__tags__create",
  "arguments": {
    "name": "Name"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold tags:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 empty_name, slug_taken, bad_colour · 429 slow down

tags.delete

Delete a tag — Removes the entry and every attachment it has. Says how many before it does.

Needs tags:manage · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/tags/{id}/actions/delete
API key or OAuth token · tags:manage
  • REST alias: DELETE /api/v1/tags/{id}
  • Canonical address: POST /api/v1/actions/tags.delete

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Tag · id of tag

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe tag must exist in this workspace.

Events it publishes: tags.deleted

What it writes: tag (delete) — Removes the entry; tag_link (delete) — Its attachments go with it, by cascade

curl -X POST https://app.getera.io/api/v1/tags/TAG_ID/actions/delete \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "tags.deleted"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.tags.delete("TAG_ID")
era tags delete TAG_ID
tools/call
{
  "name": "era__tags__delete",
  "arguments": {
    "id": "TAG_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold tags:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down

tags.detach

Remove a tag from a record — Takes a tag off a record without touching the vocabulary.

Needs tags:read · changes data

POSThttps://app.getera.io/api/v1/tag_links/actions/detach
API key or OAuth token · tags:read
  • Canonical address: POST /api/v1/actions/tags.detach

Input

FieldTypeRequiredNotes
tagIduuidyesTag · id of tag
recordTypestringyesRecord type · max 60
recordIduuidyesRecord

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
unknown_record_typeThe record type must be one a module owns.
cannot_write_recordUntagging a record needs write on that record’s module.
not_taggedThe tag must actually be on the record.

Events it publishes: tags.detached

What it writes: tag_link (delete) — Detaches the tag

curl -X POST https://app.getera.io/api/v1/tag_links/actions/detach \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"tagId":"TAG_ID","recordType":"Record type","recordId":"RECORD_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "tags.detached"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.tagLinks.detach({
  tagId: "TAG_ID",
  recordType: "Record type",
  recordId: "RECORD_ID"
})
era tag_links detach \
  --tag-id "TAG_ID" \
  --record-type "Record type" \
  --record-id "RECORD_ID"
tools/call
{
  "name": "era__tags__detach",
  "arguments": {
    "tagId": "TAG_ID",
    "recordType": "Record type",
    "recordId": "RECORD_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold tags:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 unknown_record_type, cannot_write_record, not_tagged · 429 slow down

tags.merge

Merge two tags — Moves everything from one entry onto another and removes the empty one.

Needs tags:manage · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/tags/{id}/actions/merge
API key or OAuth token · tags:manage
  • Canonical address: POST /api/v1/actions/tags.merge

Input

FieldTypeRequiredNotes
winnerIduuidyesFilled from the path {id} · Keep · id of tag
loserIduuidyesMerge away · id of tag

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
same_tagA tag cannot be merged into itself.
not_foundBoth tags must exist in this workspace.

Events it publishes: tags.merged

What it writes: tag_link (update) — Repoints the attachments; tag (delete) — Removes the entry that was merged away

curl -X POST https://app.getera.io/api/v1/tags/TAG_ID/actions/merge \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"loserId":"TAG_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "tags.merged"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.tags.merge("TAG_ID", {
  loserId: "TAG_ID"
})
era tags merge TAG_ID \
  --loser-id "TAG_ID"
tools/call
{
  "name": "era__tags__merge",
  "arguments": {
    "winnerId": "TAG_ID",
    "loserId": "TAG_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold tags:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 same_tag, not_found · 429 slow down

tags.update

Rename or recolour a tag — Changes the entry itself. Nothing already tagged is touched.

Needs tags:manage · changes data

POSThttps://app.getera.io/api/v1/tags/{id}/actions/update
API key or OAuth token · tags:manage
  • REST alias: PATCH /api/v1/tags/{id}
  • Canonical address: POST /api/v1/actions/tags.update

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Tag · id of tag
namestringName · max 120
colorstringColour · Six hex digits after a hash. Left empty, the tag takes a stable colour derived from its id. · max 7
descriptionstringDescription · max 2000
isPinnedbooleanPin to filters

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe tag must exist in this workspace.
empty_nameA tag needs a name that reduces to a key.
slug_takenTwo tags may not reduce to the same key.
bad_colourA colour must be six hexadecimal digits after a hash.

Events it publishes: tags.updated

What it writes: tag (update) — Rewrites the entry

curl -X POST https://app.getera.io/api/v1/tags/TAG_ID/actions/update \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Name"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "tags.updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.tags.update("TAG_ID", {
  name: "Name"
})
era tags update TAG_ID \
  --name "Name"
tools/call
{
  "name": "era__tags__update",
  "arguments": {
    "id": "TAG_ID",
    "name": "Name"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold tags:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, empty_name, slug_taken, bad_colour · 429 slow down

Data models

Table tag_link · Resource tag_links

  • GET /api/v1/tag_links
  • GET /api/v1/tag_links/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
tag_idstring (uuid) · uuidtag
record_typestring · text
record_idstring (uuid) · uuid
tagged_by_user_idstring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz

Primary key: id

Tag

Table tag · Resource tags

ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
namestring · text
slugstring · text
colorstring · textyes
descriptionstring · textyes
is_pinnedboolean · bool
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK (((color IS NULL) OR (color ~ '^#[0-9A-Fa-f]{6}$'::text)))
  • CHECK (((name = btrim(name)) AND (length(name) > 0)))
  • CHECK (((slug = lower(slug)) AND (slug = btrim(slug)) AND (length(slug) > 0)))

Events

Workflows and webhooks react to these events.

EventWhat happensPayload
tags.attachedA record was tagged.id, recordType, recordId
tags.createdA tag was added to the vocabulary.id, name, slug
tags.deletedA tag and its attachments were removed.id, name, detached
tags.detachedA tag was taken off a record.id, recordType, recordId
tags.mergedTwo tags became one.id, losingId, moved
tags.updatedA tag was renamed or recoloured.id, name, slug
Was this page helpful?