Docs/Developer/Atlas API - Libraries
DeveloperAPI

Atlas API - Libraries

The API for creating, importing and versioning Atlas libraries of shared type definitions.

Updated 1 Oct 2026Timebase Docs

Overview#

A library is a reusable set of ontology type definitions - entity types, properties, relationship types, data types and functions - that any number of domains can import. Library types are the T-Box you share; domain types are the ones you specialise per site.

This article is written for developers building an integration against Atlas. It covers the library catalog, the ontology surface inside a library, and the call that imports a library into a domain.

Read Atlas API Overview first. It is the canonical reference for the base address and ports, authentication, the RFC-9457 error contract, the JSON serialization rules, pagination, and the draft-session behaviour that changes what a successful write means. This article does not restate any of that; it links to it.

Three things about libraries are worth knowing up front:

  1. The catalog and the content are scoped differently. /api/v1/libraries is the catalog. /api/v1/branches/{branch}/libraries/{library}/... is the content. See the next section.
  2. Two libraries are installed with Atlas, and both are overwritten on every boot. Editing either is not durable. Everything else Atlas offers is an example you import. See Shipped libraries.
  3. A library's declared dependencies are write-once. There is no API to add or remove one after creation. See How imports resolve.

Two scopes, and which one is which#

This is the single most common mistake when writing against the library API.

Surface Scope Route
Library catalog - which libraries exist, create, delete HEAD (with an optional ?branch= on list and delete) /api/v1/libraries
Library content - the types inside a library Branch-scoped, branch in the path, always required /api/v1/branches/{branch}/libraries/{library}/...

It is not the other way round. The branch lives in the path for every read and write of library content, exactly as it does for domain content. The catalog is the one that defaults to the checked-out working tree, and ?branch= on the catalog is an opt-in refinement, not the norm.

The practical consequence: if you create a library on feature/x and then GET /api/v1/libraries with no ?branch=, it will not be in the list, because the catalog read fell back to the working tree at HEAD. Your POST did not fail. You looked in the wrong place.

Authentication#

Every library route group calls .RequireAtlasApiAuth(configuration). With Pulse:Auth:Enabled=true - the shipped default - a call without a Pulse-issued bearer whose aud includes Atlas does not succeed.

Every example below sends the bearer. Do the same in your own code. For how to obtain the token, what an unauthenticated call actually returns, and why it may be HTML rather than a JSON 401, see the Authentication section of Atlas API Overview.

TOKEN="<pulse-issued-bearer>"
ATLAS="https://<your-atlas-server>:4552"

Shipped libraries#

One library is embedded in the binary and restored on every start, and it is the only library present on a fresh deployment. atlas-common and isa95-common are listed with it below because they are the examples most often reached for, but both are imported on request like any other.

Library Root @id rdfs:label What it is
atlas-core urn:atlas:vocab Atlas Core Vocabulary The framework vocabulary: atlas:NodeBase, atlas:EdgeBase, atlas:isTimeAxis, atlas:hasPropertyConfig and friends. Not an ordinary domain library, and its URN base is urn:atlas:vocab:, not urn:atlas:ontology:atlas-core:
atlas-common urn:atlas:ontology:atlas-common Atlas Common Ontology An example, not installed. The shared measurement vocabulary, including the TVQ compound data type, atlas-common:Quality and the generic contains relationship. Imports atlas-core
isa95-common urn:atlas:ontology:isa95-common ISA-95 Common Object Model An example, not installed. The ISA-95 equipment and asset core: the class-side and instance-side hierarchies (Enterprise, Site, Area, and so on) with typed containment. The Material and Personnel models live in a separate isa95-material-personnel. Imports atlas-common

Two points worth stating explicitly:

  • Only atlas-core is installed. It is embedded in the binary and restored on every start. atlas-common, isa95-common and every other library Atlas ships is an example: present in the installation's examples, imported only when you ask for it, and yours to edit or delete afterwards.
  • atlas-templates and base-equipment do not exist. A script importing either gets 404 Library.NotFound. atlas-templates was an empty placeholder and has been removed; there is nothing to replace it with, because it never contained anything.

Warning: edits to a seed library do not survive a restart. On every boot Atlas compares each file in its shipped seed directory against the copy in your data repository, and force-overwrites yours whenever the bytes differ (File.Copy(seedFile, targetFile, overwrite: true), logged as Upgraded stale seed library to shipped version: {File}). The seed libraries are system-owned; the shipped seed is the source of truth. The overwritten file is then swept into an Auto-commit: seed data commit on the checked-out branch by the same startup routine, so the revert becomes part of your model history with no other warning.

This applies to atlas-core only. Every other library, including atlas-common and isa95-common, is an example you own once you import it, and a library you created yourself is never touched.

What to do instead: create your own library and declare atlas-common or another shipped library as a dependency. Never edit atlas-core in place, and never build a deployment step that patches it.

Where libraries live on disk#

A library is a single file, libraries/{name}.jsonld, inside the ontology git repository (/atlas/config in the platform Compose file). It is not a directory.

The filesystem path is never returned on the wire. The path field that older versions of this API emitted was removed on purpose, along with message. If your client reads response.path, it will get undefined.

Library catalog#

Base path: {ATLAS}/api/v1/libraries

Method Path Description
GET /api/v1/libraries List libraries. Optional ?branch=
POST /api/v1/libraries Create a library
GET /api/v1/libraries/{library} Get one library's name and description
DELETE /api/v1/libraries/{library} Hard-delete a library. Optional ?branch=

List libraries#

curl -H "Authorization: Bearer $TOKEN" \
  "$ATLAS/api/v1/libraries"

Response - 200 OK

[
  { "name": "atlas-common" },
  { "name": "atlas-core" },
  { "name": "isa95-common", "description": "Modelled on OPC 10030 (OPC Unified Architecture - Common Object Model: ISA-95), which conforms to ANSI/ISA-95.00.02-2010 Part 2 / IEC 62264-2:2013." }
]

Only name and description are on the wire. description is the rdfs:comment on the first owl:Ontology node in the file, not its rdfs:label. It is emitted as null when absent and then, per the global serializer settings, omitted from the JSON entirely - which is why atlas-core and atlas-common have no description key at all above. Do not assume the key is present.

Results are sorted by name.

Scoping the listing to a branch

curl -H "Authorization: Bearer $TOKEN" \
  "$ATLAS/api/v1/libraries?branch=feature/site-rollout"

With ?branch= the handler resolves the branch to a commit SHA and reads the library set off that pinned tree, so a library committed only to a non-HEAD branch appears. Without it, the listing is the working tree at HEAD.

An unresolvable branch name does not error. The handler catches the git failure and falls back to the working-tree listing. A typo in ?branch= therefore returns a plausible-looking HEAD list rather than a 404. Confirm the branch exists via GET /api/v1/branches before trusting a branch-scoped catalog read.

Get one library#

curl -H "Authorization: Bearer $TOKEN" \
  "$ATLAS/api/v1/libraries/isa95-common"

Response - 200 OK

{
  "name": "isa95-common",
  "description": "Modelled on OPC 10030 (OPC Unified Architecture - Common Object Model: ISA-95), which conforms to ANSI/ISA-95.00.02-2010 Part 2 / IEC 62264-2:2013."
}

404 Not Found when the library does not exist, with detail Library '<name>' not found.

This read has no branch parameter. It resolves against the working tree at HEAD. To confirm a library exists on a specific branch, use the branch-scoped catalog list above, or read its content through /api/v1/branches/{branch}/libraries/{library}/entity-types.

There is no message field, and there is no POST /api/sessions endpoint to be pointed at. The current editing-session concept is the branch draft session at /api/v1/branches/{branch}/draft - see Atlas API Overview.

Create a library#

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  "$ATLAS/api/v1/libraries" \
  -d '{
        "name": "site-common",
        "dependencies": ["isa95-common"],
        "description": "Types shared across our sites",
        "branch": "main"
      }'
Field Type Required Description
name string Yes Library identifier, and the file name (libraries/{name}.jsonld). Must not already exist
dependencies string[] No Names of other libraries this one builds on. Written into the new file's @context as bare sibling references. Write-once - there is no API to change them later
description string No Persisted as rdfs:comment on the root owl:Ontology node, and returned by the catalog reads
branch string No Target git branch. Omitted or null targets HEAD

Response - 201 Created

{ "name": "site-common" }

with

Location: /api/v1/libraries/site-common

The 201 body carries name and nothing else.

Errors

Status Condition Detail
400 Empty or whitespace name Library name cannot be empty
409 Name already in use Library 'site-common' already exists

Both are RFC-9457 application/problem+json, not a bare {"message": ...}.

content is accepted by the schema and then thrown away

If you send a content field carrying raw JSON-LD, nothing happens and nothing tells you. The v1 endpoint's request record does not declare content at all, and its mapping to the internal command hard-codes it:

public CreateLibraryCommand ToCommand() =>
    new(Name, Dependencies, Content: null, Description, Branch);

The command handler is capable of using supplied content, but the HTTP surface never gives it any. You get the generated scaffold instead of your document, with a 201 and no error. Do not attempt to seed a library's types through the create call. Create the library empty, then author its types through the branch-scoped ontology routes below.

What the generated scaffold contains

The new file is a minimal ontology header:

{
  "@context": [
    "atlas-core.jsonld",
    "isa95-common.jsonld",
    { "site-common": "urn:atlas:ontology:site-common:" }
  ],
  "@id": "urn:atlas:ontology:site-common:library",
  "@graph": [
    {
      "@id": "urn:atlas:ontology:site-common",
      "@type": "owl:Ontology",
      "rdfs:label": "site-common Library",
      "rdfs:comment": "Types shared across our sites"
    }
  ]
}

Three things to note:

  • atlas-core is always imported, whether or not you list it. That is why the framework vocabulary is available in every library without being declared. Listing it in dependencies is harmless: the handler strips a trailing .jsonld and de-duplicates.
  • Every type you later create in this library gets the URN urn:atlas:ontology:site-common:{LocalName}. All five element kinds share that one namespace per scope, so an entity type and a property cannot both be called Temperature in the same library.
  • The URN prefix is normalised but the library's name is not. The prefix is lowercased and spaces become hyphens, while the file name and the catalog name keep whatever casing you sent. Creating Site Common produces a library named Site Common, stored as Site Common.jsonld, whose types are urn:atlas:ontology:site-common:.... Nothing rejects this; it is simply confusing forever after. Send lowercase, hyphen-separated names.

A 201 does not always mean the library is on the branch

Library writes go through the shared write orchestrator, which means the draft-session behaviour described in Atlas API Overview applies. A write that carries outstanding error-severity validation violations lands in the caller's private draft session and does not advance the target branch, signalled only by the Atlas-Validation-Violations response header. Check that header, and publish with POST /api/v1/branches/{branch}/draft/publish when you have one.

Delete a library#

curl -X DELETE -H "Authorization: Bearer $TOKEN" \
  "$ATLAS/api/v1/libraries/site-common?branch=main"

204 No Content on success. ?branch= is optional and defaults to HEAD.

Unlike the create and get paths, delete does not pre-check existence at HEAD. This is intended: so a library that lives only on a feature branch can still be deleted on its own branch; existence is checked inside the branch's worktree. A genuinely missing target returns 404 Library.NotFound.

Blocked deletes return 409 with a dependents list

If anything still imports the library, nothing is deleted and you get the delete-blocked problem type:

{
  "type": "https://atlas.timebase.io/errors/delete-blocked",
  "title": "Delete Blocked",
  "status": 409,
  "detail": "Cannot delete 'site-common' — it is referenced by 2 libraries and 1 domain.",
  "target": { "name": "site-common", "kind": "library" },
  "dependents": [
    { "id": "site-north", "name": "site-north", "kind": "library", "referenceType": "library-imports-library" },
    { "id": "site-south", "name": "site-south", "kind": "library", "referenceType": "library-imports-library" },
    { "id": "lonestar",   "name": "lonestar",   "kind": "domain",  "referenceType": "domain-imports-library" }
  ]
}

referenceType is one of domain-imports-library or library-imports-library. The dependents array is never truncated, so it is safe to render directly to a user.

Branch on the type URI rather than on the status code: a plain name conflict is .../errors/conflict with the same 409.

A third 409: an open draft session. A library delete is refused while the calling identity holds a draft session on the resolved branch, with the detail naming the draft and both exits (publish or discard). Creates, updates and imports are unaffected, because those merge on publish; a deletion has nothing to fix and publish later, so it is never parked in a draft. This one carries the plain .../errors/conflict type with no code and no extensions, so on a DELETE treat that type as "you have an open draft" and confirm with GET /api/v1/branches/{branch}/draft. Only your own draft blocks you.

The dependency scan is wider than "declared dependencies". A domain blocks the delete if any *.jsonld file anywhere under domains/{domain}/ (recursive, not just ontology.jsonld) has an @context entry containing libraries/ whose filename matches. A library blocks it if any *.jsonld under libraries/ resolves a .jsonld @context reference to the target file. Matching is case-insensitive. This is why you can get a 409 naming a domain you were sure did not import the library: something else in that domain's subtree references it.

Update a library's description#

PUT /api/v1/libraries/{library}?branch={branch}

The description is the only editable piece of a library's metadata. The name is immutable, and more firmly than a domain's: every importing domain carries the library's path and its CURIE prefix, so a rename would break references in two ways at once.

{
  "description": "Shared pump and valve types for the bottling plant."
}

The response echoes the description as persisted, trimmed, and null when cleared.

atlas-core and atlas-common refuse this call with a 403 and a Library.ReadOnly error. They ship embedded and are restored on every start, so nothing written to them would survive. Every other library, shipped or your own, accepts it.

Library type definitions#

Base path: {ATLAS}/api/v1/branches/{branch}/libraries/{library}

The library ontology surface is symmetric with the domain ontology surface: each of the five kinds is mapped twice, once under domains/{domain} and once under libraries/{library}, with the same handlers, the same request bodies and the same response bodies. For the full field-by-field description of each body, see Atlas API - Ontology; this section gives the routes, the verbs and the library-specific behaviour.

{branch} is required in the path on every one of these routes. A branch name containing / must be percent-encoded (feature%2Fsite-rollout); the endpoints decode it back.

The five resources#

Kind Verbs (relative to the base path) Update verb Source
data-types GET, GET /{name}, POST, PUT /{name}, DELETE /{name} PUT
entity-types GET, GET /{name}, POST, PATCH /{name}, DELETE /{name}, plus PUT and DELETE /{name}/properties/{property} PATCH
properties GET, GET /{name}, POST, PATCH /{name}, DELETE /{name} PATCH
relationship-types GET, GET /{name}, POST, PATCH /{name}, DELETE /{name} PATCH
functions GET, GET /{name}, POST, PUT /{name}, DELETE /{name} PUT

If you have an older integration, four renames apply here and correcting only the version prefix is not enough:

Old Current
.../nodes .../entity-types
.../relationships .../relationship-types
PUT .../entity-types/{name}, PUT .../properties/{name}, PUT .../relationship-types/{name} PATCH
(absent) the whole .../data-types group

The verb rule. Entity types, properties and relationship types update with PATCH, a partial merge in which unsent fields are preserved. Data types and functions update with PUT, a full atomic re-emit of the whole definition. Sending the wrong verb does not return 405: it matches no route, and Atlas answers with the single-page application shell as 200 text/html. Check Content-Type before parsing.

data-types is a full ontology resource in its own right, carrying compound types such as TVQ and the isTimeAxis field flag. Historian-bound measurements depend on it. An integration written before mid-2026 almost certainly does not know it exists. Note that its library twins for PUT and DELETE were added late; before that, library data-type edits returned 404.

functions are full CRUD in library scope, not read-only. They always were on the library side. Deleting a function that an instance-map CalculatedStrategy still binds returns the delete-blocked 409 with target.kind = "function" and dependents[].kind = "instanceMap", referenceType = "calculated-strategy".

Reading a library's entity types#

curl -H "Authorization: Bearer $TOKEN" \
  "$ATLAS/api/v1/branches/main/libraries/isa95-common/entity-types"

Each entity type is returned as:

{
  "uri": "urn:atlas:ontology:isa95-common:Area",
  "label": "Area",
  "comment": "A physical, geographical, or logical grouping...",
  "parentType": "urn:atlas:vocab:NodeBase",
  "properties": [ /* PropertyDefinition[] */ ],
  "sourceLibrary": null,
  "kind": "entity"
}

kind is a camelCase string ("entity" or "dataType"), never an integer. sourceLibrary is the short name of the imported library that declared the type, and is null for types declared in the scope you are reading. Null members are omitted from the JSON entirely.

Creating an entity type in a library#

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  "$ATLAS/api/v1/branches/main/libraries/site-common/entity-types" \
  -d '{
        "name": "Filler",
        "label": "Filler",
        "comment": "Bottle filling machine",
        "parentType": "Equipment",
        "properties": ["Throughput", "Status"]
      }'

201 Created returns the created definition in the body, with no Location header (ontology creates do not emit one).

properties is an optional list of existing property local names or full URIs to attach to the new type in the same git commit. It works cross-scope, so a property imported from a dependency library attaches to a type you are declaring here. An unresolvable entry fails the whole create; the type is never half-made.

Attaching a property to a type#

Attachment is addressed under the entity type, not under the property.

# attach - idempotent, cross-scope, 204 No Content
curl -X PUT -H "Authorization: Bearer $TOKEN" \
  "$ATLAS/api/v1/branches/main/libraries/site-common/entity-types/Filler/properties/Throughput"

# detach - 204 No Content, 404 when the property is not declared on the type
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
  "$ATLAS/api/v1/branches/main/libraries/site-common/entity-types/Filler/properties/Throughput"

This is the answer to "I created a property in the library, why does nothing have it". Creating a property attaches it to nothing unless you pass attachToTypes at create time or attach it explicitly afterwards.

Note the write-side key is attachToTypes (a list); the read-side key on PropertyDefinition is attachedTypes (a list of full URIs). There is no domain field on a property request - that name was retired.

Attachment is a declared relationship. attachedTypes never reports inherited attachments, and a PATCH with an empty attachToTypes list detaches every declared attachment while leaving inherited ones alone.

A bad parentType is not rejected at write time#

If you reference a parent type that is not in this library or any of its dependencies, the create succeeds. The resolver falls back to a library-scoped URI that resolves to nothing.

You find out later, when the domain that imports this library fails to compile. Read the outstanding problems with the validation route below, before you compile.

Validation#

curl -H "Authorization: Bearer $TOKEN" \
  "$ATLAS/api/v1/branches/main/libraries/site-common/validation"

A read-only, side-effect-free run of the validation pipeline over the library, returning every outstanding violation and a WarningCount. It compiles nothing and caches nothing.

This is the actionable half of warn-on-save. A save that persists with error-severity violations gives you only a count, in the Atlas-Validation-Violations response header; this route gives you the list. It is the first thing to call when a library write succeeded but something downstream is wrong.

Rule IDs are O-A* and O-B* for ontology rules, I-A* and I-B* for instance rules. If a document quotes a rule name in any other shape, it is invented.

The read is draft-aware with no extra parameters: the owner of a draft session that is strictly ahead of the target sees their own in-progress breakage, everyone else sees the target branch.

Importing a library into a domain#

A library does nothing until a domain imports it. The imports resource is branch-scoped and lives under the domain.

Method Path Response
GET /api/v1/branches/{branch}/domains/{domain}/imports 200 with a JSON array of library name strings
POST /api/v1/branches/{branch}/domains/{domain}/imports 201 with { "domain": "...", "library": "..." }
DELETE /api/v1/branches/{branch}/domains/{domain}/imports/{library} 204 No Content
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  "$ATLAS/api/v1/branches/main/domains/lonestar/imports" \
  -d '{ "library": "isa95-common" }'

The body is { "library": "<name>" } and nothing else.

Errors

Status Condition Detail
404 Domain does not exist Domain '<name>' not found
404 Domain has no ontology.jsonld Domain '<name>' has no ontology.jsonld
404 Library does not exist Library '<name>' not found
409 Already imported Library '<name>' already imported

Then rebuild the compiled world with PUT /api/v1/branches/{branch}/domains/{domain}/world.

The domain file on disk is ontology.jsonld. It is not schema.jsonld; any repository tree diagram showing that name is wrong.

How imports resolve#

This resource is not a package manager. What it does provide:

  • An import is a plain relative file reference. A domain importing a library appends "../../libraries/{name}.jsonld" to its ontology's @context. A library importing a sibling library appends the bare "{name}.jsonld". The read side recovers the names by taking the filename without extension.
  • No version is recorded anywhere. There is no pinning, no range syntax, no lock file. An import always resolves to whatever that file contains on the branch you are reading.
  • Resolution is by bare name. Case-insensitive on the delete-guard scan; exact on the @context string when checking for a duplicate import.
  • The closure is transitive and is loaded at compile time. A library reachable only through another imported library is still loaded, and is still compared for staleness.
  • A URN collision across the closure is last-writer-wins. Because all five element kinds share one URN namespace per scope, two libraries that both declare urn:atlas:ontology:x:Temperature are indistinguishable to the loader.
  • Dependencies are write-once. Application/Libraries/ contains exactly CreateLibraryCommand(Handler), DeleteLibraryCommand(Handler), GetAllLibrariesQuery(Handler) and GetLibraryInfoQuery(Handler). There is no add-dependency or remove-dependency command, and no route mounts one. If you omit a dependency at create time, your options are to edit libraries/{name}.jsonld in the git repository directly, or to delete and recreate the library.

The /app twin is not a contract#

If you watch network traffic from the Atlas user interface you will see /app/libraries and /app/branches/{branch}/libraries/{library}/..., not /api/v1/.... Those are the same handlers behind the same bodies, but they are the private browser plane: cookie authentication, admin-gated mutations, excluded from the published API documents. They are not a public contract and can change without notice.

Integrate against /api/v1. See Atlas API Overview for the full surface list.

Troubleshooting#

Symptom Likely cause What to do
My edits to atlas-core disappeared after a restart The seed library is force-overwritten from the shipped copy on every boot and the revert is auto-committed Never edit atlas-core. Create your own library instead. Imported libraries such as atlas-common and isa95-common are yours and are not overwritten
POST /api/v1/libraries returns 409 A library with that name already exists at HEAD Call GET /api/v1/libraries to check. The existence check looks at HEAD rather than the branch you are writing to, so pick a name that is unused across every branch
POST /api/v1/libraries returned 201 but the library is not in GET /api/v1/libraries You created it on a branch; the catalog list defaults to HEAD Pass ?branch=<your-branch> on the list. If it is still missing, check the Atlas-Validation-Violations header on the create response and GET /api/v1/branches/{branch}/draft
I sent content on create and got the empty scaffold back The endpoint hard-codes Content: null; content is ignored Create the library empty, then author its types through the branch-scoped ontology routes
My client reads response.path and gets undefined path was removed from every library response, along with message Use name. A library is one file, libraries/{name}.jsonld, but that is not on the wire
A library route returns 200 text/html instead of JSON The path matched no route and the single-page-application fallback answered it. Usually nodes instead of entity-types, relationships instead of relationship-types, PUT where PATCH is expected, or a missing /branches/{branch} segment Check Content-Type before parsing, then check the route against the tables above
GET /api/v1/libraries/{library}/entity-types returns HTML Library content is branch-scoped; there is no non-branch-scoped content route Use /api/v1/branches/{branch}/libraries/{library}/entity-types
A type created in a library does not appear in a domain The domain has not imported the library, or has not been recompiled since POST /api/v1/branches/{branch}/domains/{domain}/imports with { "library": "<name>" }, then PUT /api/v1/branches/{branch}/domains/{domain}/world
POST .../imports returns 404 Library.NotFound for a library I can see The import check reads the working tree at HEAD, not your branch Confirm the library exists at HEAD, or merge your branch first
POST .../imports returns 404 naming atlas-templates, base-equipment or opc10030-isa95-common None of those ships. The first was removed, the second never existed, the third is now isa95-common Use isa95-common, or list what is available
A parentType pointing at a type in another library was accepted, then the domain failed to compile Bad parent references are not rejected at write time; they fall back to an unresolvable scoped URI GET /api/v1/branches/{branch}/libraries/{library}/validation, then either declare the parent locally or add the dependency. Dependencies are write-once, so this may mean recreating the library
DELETE /api/v1/libraries/{library} returns 409 naming a domain I am sure does not import it The scan inspects the @context of every *.jsonld under domains/{domain}/ recursively, not just ontology.jsonld Read the dependents array in the problem body; it lists exactly what blocks you and is never truncated
My DELETE handler treats 409 as "name already exists" A blocked delete uses the distinct type .../errors/delete-blocked and carries target and dependents Branch on the type URI, not the status code
?branch=typo returned a library list that looks like HEAD An unresolvable branch falls back to the working-tree listing rather than erroring Verify the branch with GET /api/v1/branches first
My library name has capitals and its type URNs do not match it The URN prefix is lowercased and space-hyphenated; the file name and catalog name are not Use lowercase, hyphen-separated names. Nothing enforces it
esc
↑↓ navigate · ↵ open · ⌘K anywhere