Skip to main content

REST API

This document was generated using AI assistance

Content may include inaccuracies, outdated information, or technical errors. Users are advised to cross-check critical information before implementation.

Architeezy exposes a REST API under /api on the same host as the application. It covers scopes, projects, models, representations, project membership and users, and it serves model content in every format the server can write.

The generated OpenAPI description is at /v3/api-docs, and an interactive browser for it at /swagger-ui/index.html.

Authentication

Read requests work without credentials and return the entities that are public. Everything else - creating, updating, deleting, and reading entities with a higher confidentiality level - requires a signed-in caller. There are two ways to sign in.

An application served from the same host as Architeezy inherits the browser session. Send the cookie by adding credentials: "include" to your requests:

const response = await fetch('/api/users/current', { credentials: 'include' });
const user = response.status === 204 ? null : await response.json();

GET /api/users/current answers 204 No Content for an anonymous caller and 200 with the user profile for a signed-in one. That is how an application finds out at startup whether the user is signed in.

Bearer token

Tools that run outside the browser - a script, a desktop application, a CI job

  • authenticate with an OpenID Connect access token and send it as a header:
GET /api/models HTTP/1.1
Host: architeezy.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAi...

The public client to use is architeezy-api. It accepts the authorization code flow with loopback redirect URIs (http://localhost:* and http://127.0.0.1:*). A local tool opens the system browser, catches the redirect on a port it listens on, and exchanges the code for tokens.

Ask for the offline_access scope in addition to openid and you receive an offline refresh token. It survives the end of the browser session, so an unattended job can issue fresh access tokens with nobody signed in. Store it like a password.

The endpoints and the client id are published, so a client can discover them instead of hard-coding them:

$ curl https://architeezy.com/api/settings
{"aboutUrl":"https://about.architeezy.com",
"documentationUrl":"https://docs.architeezy.com",
"applicationsUrl":"https://apps.architeezy.com",
"oauth2IssuerUrl":"https://auth.architeezy.com/realms/architeezy",
"oauth2ApiClientId":"architeezy-api"}

The issuer URL serves the standard OpenID Connect discovery document at /.well-known/openid-configuration, which carries the authorization and token endpoints.

Resources

PathResource
/api/scopesScopes, the top-level containers for projects
/api/projectsProjects, their imports and their members
/api/modelsModels, their metadata and their content
/api/representationsDiagrams, tables, forms and documents
/api/project-imagesImages stored in a project
/api/project-usersProject membership
/api/project-enrollmentsRequests to join a project, and their approval
/api/usersUser profiles, and the current user

Entities are addressed by their UUID, for example GET /api/models/019b151f-aa97-77b2-9a08-c676443c4a00. Projects, models and representations can also be addressed by their slug chain, which is what you see in the browser address bar:

PatternResolves to
/api/projects/{scope}/{project}/{version}A project
/api/models/{scope}/{project}/{version}/{model}A model
/api/representations/{scope}/{project}/{version}/{model}Its default representation
/api/representations/{scope}/{project}/{version}/{model}/{repr}A representation by slug

HAL responses

Collections and single entities are returned as HAL+JSON: the entity fields plus a _links map, and for collections an _embedded map keyed by the resource name.

curl 'https://architeezy.com/api/models?size=2&sort=name,asc'
{
"_embedded": {
"models": [
{
"_links": {
"self": {
"href": "https://architeezy.com/api/models/019b151f-aa97-77b2-9a08-c676443c4a00"
},
"content": [
{
"href": "https://architeezy.com/api/models/demo/eip-system-design/dev/airline-ticket-aggregator/content?format=json{&inline}",
"templated": true,
"title": "JSON",
"type": "application/json"
}
]
},
"id": "019b151f-aa97-77b2-9a08-c676443c4a00",
"scope": { "id": "019a8255-...", "slug": "demo", "name": "Demo" },
"project": {
"id": "019b12f8-...",
"slug": "eip-system-design",
"version": "dev",
"name": "EIP System Design"
},
"slug": "airline-ticket-aggregator",
"name": "Airline Ticket Aggregator",
"confidentiality": 0,
"contentType": "https://architeezy.com/metamodel/eip/dev/eip#EnterpriseIntegrationModel",
"defaultRepresentation": {
"id": "019b151f-ac0b-7a0e-aab7-78040deac6db",
"slug": "enterpriseintegrationmodel-diagram",
"name": "EnterpriseIntegrationModel Diagram"
},
"creator": { "id": "27ebbd12-...", "name": "denis" },
"creationDateTime": "2025-12-13T00:32:33.974480Z",
"lastModificationDateTime": "2025-12-13T01:27:55.940772Z"
}
]
},
"_links": {
"first": {
"href": "https://architeezy.com/api/models?page=0&size=2&sort=name,asc"
},
"self": {
"href": "https://architeezy.com/api/models?page=0&size=2&sort=name,asc"
},
"next": {
"href": "https://architeezy.com/api/models?page=1&size=2&sort=name,asc"
},
"last": {
"href": "https://architeezy.com/api/models?page=19&size=2&sort=name,asc"
}
},
"page": { "number": 0, "size": 2, "totalElements": 39, "totalPages": 20 }
}

Each model carries one content link per output format the server can produce for it. The links are URI templates: strip the {&inline} part, or fill it in, before you fetch them.

Pagination

Lists take page (zero-based) and size, and answer with a page block holding number, size, totalElements and totalPages. Rather than computing page numbers, follow _links.next until it is absent:

async function fetchAllModels() {
const models = [];
let url = 'https://architeezy.com/api/models?size=100';
while (url) {
const response = await fetch(url, { credentials: 'include' });
const page = await response.json();
models.push(...(page._embedded?.models ?? []));
url = page._links?.next?.href ?? null;
}
return models;
}

Sorting

sort=<field>,<direction> orders the result, for example sort=name,asc or sort=lastModificationDateTime,desc. Repeat the parameter to sort by several fields in order.

Filtering

Any other query parameter is read as a filter on a field of the resource. For models the fields include id, slug, name, description, confidentiality, contentType, creationDateTime, lastModificationDateTime, and the dotted forms project.slug, project.version, project.name, scope.slug, scope.name, creator.name and lastModifier.name.

The value carries the operator:

ValueMeaning
name=OrderEqual to Order
name=!OrderNot equal to Order
name=Order*Starts with Order, ignoring case
name=*Order*Contains Order, ignoring case
name=!*Order*Does not contain Order
confidentiality=>=2Greater than or equal to 2, also >, <, <=
description=Empty or absent
description=!Present

Repeating a parameter combines the conditions with AND. For non-text fields a comma inside one value combines them with OR, so confidentiality=0,1 matches either. Commas in a text value are literal. Date-time fields are the exception: their comma-separated parts combine with AND, which is how you express a range.

Naming several fields in the key applies one condition across all of them and matches when any field satisfies it:

curl 'https://architeezy.com/api/models?name,description=*payment*&size=20'

An unknown field is an error rather than a silently ignored parameter:

{
"detail": "Invalid filter parameters",
"instance": "/api/models",
"status": 400,
"title": "Bad Request",
"errors": [{ "message": "Unknown filter field: nope", "field": "nope" }]
}

Model content

Metadata and content are separate resources. GET /api/models/{id} returns the metadata; GET /api/models/{id}/content returns the serialized model, with the same endpoint available on the slug chain:

curl 'https://architeezy.com/api/models/demo/eip-system-design/dev/airline-ticket-aggregator/content?format=json'
{
"json": { "version": "1.0", "encoding": "utf-8" },
"ns": { "eip": "https://architeezy.com/metamodel/eip/dev/eip" },
"content": [
{
"id": "961dae55-b6f7-53bd-8d80-cd9f70320f93",
"eClass": "eip:EnterpriseIntegrationModel",
"data": {
"name": "Airline Ticket Aggregator",
"entities": [
{
"id": "350261b6-fad7-52d8-a8a8-80cb9206e661",
"eClass": "eip:Endpoint",
"data": { "name": "API Gateway" }
}
]
}
}
]
}

The format comes from ?format= or from the Accept header. Add ?inline=true to have the response displayed by the browser instead of downloaded.

formatContent typeContents
jsonapplication/jsonThe model as JSON
xmiapplication/vnd.xmi+xmlThe model as XMI
ttltext/turtleThe model as RDF Turtle
trigapplication/trigThe model as RDF TriG
archimateapplication/xmlAn ArchiMate model in Archi's own file format
ifcapplication/x-stepAn IFC model as a STEP file

Every model can be written as json, xmi, ttl and trig. The notation-specific formats are offered only for the models they fit: archimate for an ArchiMate model, ifc for an IFC model. A request for a format the server cannot produce for that model answers 406 Not Acceptable.

Two endpoints answer questions about a model instead of returning a file. GET /api/models/{id}/owl returns the OWL ontology derived from the model, and POST /api/projects/{id}/sparql runs a SPARQL query across a project.

Writing content

PUT /api/models/{id}/content?format=archimate replaces a model's content with the raw body of the request. To create a model and fill it in one call, post multipart/form-data to /api/models with a JSON part named entity and the file in a part named content:

$ curl -X POST https://architeezy.com/api/models \
-H "Authorization: Bearer $TOKEN" \
-F 'entity={"projectId":"019b12f8-f501-7553-9f35-4cde33edea0c",
"name":"Ordering"};type=application/json' \
-F 'content=@ordering.archimate'

The upload format is taken from the file extension unless you set contentFormat in the entity part. A format no deserializer accepts answers 415 Unsupported Media Type.

Status codes

200 on a read or update, 201 on a create, 204 on a delete. 400 marks a malformed request or a failed validation, 409 a conflict such as a slug already taken, 415 an unsupported upload format, 406 an unavailable output format.

401 means an anonymous caller tried to write. 403 means you can see the entity but may not change it. 404 covers both an entity that does not exist and one you are not allowed to see, so probing identifiers tells you nothing about what exists.

Errors use application/problem+json. A 400 or 409 carries an errors array with the offending fields; a 500 carries an errorId you can quote to whoever runs the server.