LLARS v1 Conference Manager API¶
Version: 1.0 | Last updated: October 2026
Programmatic REST API for the conference manager: read research groups,
maintain conference series and editions (confirmed dates and estimated
windows), manage papers with authors, arXiv/DOI links and submission history,
and start, watch and cancel the AI refresh. All endpoints live under
/api/v1/conference-manager/*.
Authentication¶
Identical to the Scenario API: X-API-Key (personal or system) or OAuth
bearer. See v1 Scenario API.
Personal keys are created under Settings → API Keys, where you pick the
scopes conference:read and/or conference:write.
Scopes¶
| Scope | Allows | RBAC fallback (OAuth, keys without scopes) |
|---|---|---|
conference:read |
every GET under /api/v1/conference-manager/* |
feature:conference_manager:view |
conference:write |
every POST / PATCH / PUT / DELETE, including starting and cancelling the AI refresh; implies read |
feature:conference_manager:edit |
admin:* |
overrides everything | admin:permissions:manage |
Conference scopes are strictly separate from scenario:* and chatbot:*.
Scope and permission together: a scope narrows a key, it never widens it.
On top of the scope, the account that owns the key must hold the matching
feature permission (feature:conference_manager:view to read, …:edit to
write; admins always). Revoke the permission and the account's conference keys
stop working immediately (403).
Research groups and visibility¶
Every series, edition, paper and AI run belongs to exactly one research group. Every call is checked against that group:
| Situation | Response |
|---|---|
You are a member (role owner or member) |
read and write |
You are a member with role viewer |
read; writes → 403 |
| You are not a member | 404 — exactly like an ID that does not exist |
| Admin / system key | all groups |
The 404 for foreign groups is deliberate: status codes must not reveal which
IDs exist in other groups. References in a body (series_id,
conference_id) must point into the same group as the object, otherwise
404 as well. Legacy records without a group are visible to admins only; the API
never creates group-less records (group_id is required on create and cannot be
changed later).
Response and error format¶
Successful responses carry "success": true and the object under its name
(conference, series, paper, run, research_group). Lists are
paginated:
limit is at most 200 (larger values are capped, default 50), offset starts
at 0. Errors look the same everywhere:
{
"success": false,
"error": "Invalid request body — Value error, submission_window_start must be on or before submission_window_end",
"error_type": "validation_error",
"details": { "errors": [ { "loc": [], "msg": "...", "type": "value_error" } ] }
}
| Status | Meaning |
|---|---|
200 / 201 |
read / created |
202 |
AI run accepted (runs in the background) |
400 |
invalid body or query parameter (unknown field, bad date, window with start > end, invalid DOI …) |
401 |
missing or invalid key |
403 |
scope missing, feature permission missing, or group is read-only (viewer) |
404 |
does not exist or belongs to a foreign group |
409 |
conflict: edition (acronym, year) or series acronym already exists; an AI run is already active in the group |
503 |
AI refresh is not available on this server |
Dates: confirmed or estimated¶
Every edition has three milestones. Each can have an exact date (confirmed) and/or a window (estimated):
| Milestone | Exact date | Window |
|---|---|---|
| Submission | submission_deadline |
submission_window_start, submission_window_end |
| Author notification | notification_date |
notification_window_start, notification_window_end |
| Conference | start_date, end_date |
conference_window_start, conference_window_end |
- Formats: exact dates are naive ISO datetimes
YYYY-MM-DDTHH:MM:SS(always returned that way). On write, a plain date (2027-05-15→00:00:00) or an offset (Z,+02:00) is accepted too; an offset is converted to UTC and then dropped. Window bounds are plain datesYYYY-MM-DD— a time of day is a 400 here. - Rules:
start ≤ endper window andstart_date ≤ end_date, otherwise - A PATCH that changes only one bound is checked against the stored counterpart. Unparsable values are always a 400 — never a silent erase.
- The exact date wins: once the real date is announced, set it via PATCH. The window stays as history; the milestone counts as confirmed from then on.
- Derived fields in the response:
milestonesper milestone withstatus(confirmed|estimated|unknown),dateorstart/end, andwindow_start/window_end;is_estimatedistrueas soon as one milestone is only estimated.estimation_basis(free text, e.g. "derived from 2023–2026") andestimation_confidence(low|medium|high) explain the estimate. Also included:core_url(CORE portal),maps_url(Google Maps),last_refreshed_atandlast_refresh_summary.
PATCH semantics (everywhere): only the fields you send are changed. An
explicit null clears an optional field (e.g. "notes": null). Required
fields (name, acronym, year, title) and an edition's core_ranking
cannot become null — send "Unranked" to remove the ranking. Unknown
fields are a 400.
Endpoints¶
Research groups¶
| Method | Path | Scope | Purpose |
|---|---|---|---|
GET |
/api/v1/conference-manager/research-groups |
conference:read |
Your groups with user_role (admins: all) |
GET |
/api/v1/conference-manager/research-groups/{id} |
conference:read |
Group with user_role, stats and members |
Groups and memberships are managed in the UI; the v1 API does not create groups.
Series¶
| Method | Path | Scope | Purpose |
|---|---|---|---|
GET |
/api/v1/conference-manager/series?group_id=&search= |
conference:read |
Series (without group_id: all accessible groups) |
POST |
/api/v1/conference-manager/series |
conference:write |
Create a series (group_id, name, acronym, optional core_ranking, website_url, keywords, notes) |
GET |
/api/v1/conference-manager/series/find?acronym=&group_id= |
conference:read |
Series by acronym (case-insensitive); series: null if none matches |
GET |
/api/v1/conference-manager/series/{id} |
conference:read |
Series with its editions |
PATCH |
/api/v1/conference-manager/series/{id} |
conference:write |
Partial update |
DELETE |
/api/v1/conference-manager/series/{id} |
conference:write |
Delete; editions stay and only lose the series link |
Series acronyms are unique server-wide — a taken acronym yields 409.
Conferences (editions)¶
| Method | Path | Scope | Purpose |
|---|---|---|---|
GET |
/api/v1/conference-manager/conferences |
conference:read |
Editions (filters below) |
POST |
/api/v1/conference-manager/conferences |
conference:write |
Create an edition (group_id, name, acronym, year + any field from "Dates") |
GET |
/api/v1/conference-manager/conferences/{id} |
conference:read |
Read an edition |
PATCH |
/api/v1/conference-manager/conferences/{id} |
conference:write |
Partial update of every field including windows |
DELETE |
/api/v1/conference-manager/conferences/{id} |
conference:write |
Delete; papers stay and only lose the conference link |
GET |
/api/v1/conference-manager/conferences/{id}/papers |
conference:read |
Papers of this edition |
Further edition fields: series_id, core_ranking (A*, A, B, C,
Unranked), city, country, website_url (http(s):// only), keywords
(≤ 50), notes. (acronym, year) is unique server-wide → 409.
Filters for GET /conferences (all optional, combinable):
| Parameter | Effect |
|---|---|
group_id |
only this group (foreign group → 404); without it: all accessible groups |
year |
edition year |
search |
substring of name, acronym, city or country |
estimated |
true: only editions with at least one estimated milestone; false: none of those |
from, to |
YYYY-MM-DD; an edition matches when its overall span (all dates and windows) overlaps [from, to]. Editions without any date drop out. |
core_ranking |
A*, A, B, C, Unranked |
Sorted by submission deadline (estimated editions by their window start), editions with neither at the end.
Papers, authors, submissions¶
| Method | Path | Scope | Purpose |
|---|---|---|---|
GET |
/api/v1/conference-manager/papers?group_id=&conference_id=&status=&search= |
conference:read |
Papers (search in title and description) |
POST |
/api/v1/conference-manager/papers |
conference:write |
Create a paper |
GET |
/api/v1/conference-manager/papers/{id} |
conference:read |
Paper with authors, submissions, conference |
PATCH |
/api/v1/conference-manager/papers/{id} |
conference:write |
Partial update; authors replaces the list |
DELETE |
/api/v1/conference-manager/papers/{id} |
conference:write |
Delete including authors and submissions |
PUT |
/api/v1/conference-manager/papers/{id}/authors |
conference:write |
Replace the whole author list |
POST |
/api/v1/conference-manager/papers/{id}/submissions |
conference:write |
Append a submission |
PATCH |
/api/v1/conference-manager/papers/{id}/submissions/{sid} |
conference:write |
Update a submission |
DELETE |
/api/v1/conference-manager/papers/{id}/submissions/{sid} |
conference:write |
Delete a submission |
- Fields:
group_id,title,status(planning,in_progress,submitted,accepted,rejected,published),conference_id,overleaf_url,external_url,arxiv_url,doi,keywords,description,notes,authors. - arXiv and DOI:
arxiv_urlacceptsarXiv:2401.12345,2401.12345or an arxiv.org link and storeshttps://arxiv.org/abs/<id>.doiacceptsdoi:10.…,https://doi.org/10.…or the bare DOI and stores the bare DOI. The response also containsarxiv_idanddoi_url(https://doi.org/<doi>). Invalid values are a 400. - Authors: ordered list; each entry has exactly one of
user_id,username(LLARS account) orexternal_name(external person), optionallyauthor_order(default: list position) andis_corresponding. Unknown or duplicated accounts are a 400; at most 50 authors. - Submissions:
conference_id,status(submitted,accepted,rejected,withdrawn),submitted_at,decided_at(exact dates),notes. The paper takes over conference and status of the newest submission. A submission is reachable only through its own paper — a foreignsidin the path yields 404.
AI refresh¶
| Method | Path | Scope | Purpose |
|---|---|---|---|
POST |
/api/v1/conference-manager/conferences/refresh |
conference:write |
Start a run for a group → 202 |
POST |
/api/v1/conference-manager/conferences/{id}/refresh |
conference:write |
Run for exactly this edition → 202 |
GET |
/api/v1/conference-manager/refresh-runs?group_id=&limit= |
conference:read |
Latest runs of the group, newest first, without progress (group_id required, limit ≤ 100, default 20) |
GET |
/api/v1/conference-manager/refresh-runs/{id} |
conference:read |
One run with progress per edition |
POST |
/api/v1/conference-manager/refresh-runs/{id}/cancel |
conference:write |
Request cancellation |
POST |
/api/v1/conference-manager/refresh-runs/{id}/resume |
conference:write |
Resume a cancelled or failed run → 202 |
Start: body {"group_id": 1, "conference_ids": [12, 13], "include_past_months": 3}.
Without conference_ids the run takes every edition of the group whose latest
known date (exact or window) is at most include_past_months months in the
past (0–36, default 3), plus editions without any date. With
conference_ids exactly those (at most 200; they must belong to the group,
otherwise 404). If a run is already active in the group you get 409 with
details.run_id of the active run. The AI never runs on its own — every run
is started manually.
Lifecycle: queued → running → done | failed | cancelled.
Editions are processed one after another (web search, reading pages, LLM
comparison with the stored data). Only values the AI finds as confirmed with a
source overwrite confirmed dates; estimates only fill empty windows. For the ARR
series the run creates missing cycles (with a confirmed submission deadline)
as new editions.
Run object:
{
"id": 7, "group_id": 1, "scope": "all", "conference_ids": [12, 13],
"status": "running", "is_active": true, "started_by": "alice",
"model_id": "Global/Mistral/Mistral-Small-3.2-24B-Instruct-2506",
"started_at": "2026-10-02T09:15:00", "finished_at": null, "cancel_requested": false,
"summary": { "total": 2, "done": 1, "changed": 1, "no_change": 0, "errors": 0 },
"open_conference_ids": [],
"progress": {
"12": {
"status": "done", "step": "…", "acronym": "EMNLP", "year": 2027, "name": "…",
"sources": ["https://2027.emnlp.org/calls/main_conference_papers/"],
"changes": [
{ "field": "submission_deadline", "old": null, "new": "2027-05-19T23:59:00",
"confidence": "confirmed", "source_url": "https://2027.emnlp.org/…",
"evidence": "Paper submission deadline: May 19, 2027" }
],
"error": null, "started_at": "…", "finished_at": "…", "updated_at": "…"
},
"13": { "status": "searching", "step": "…", "sources": [], "changes": [], "error": null }
}
}
progress[...].status per edition: queued, searching, reading,
analyzing, done (with changes), no_change, error, skipped. An API
client polls GET /refresh-runs/{id} every few seconds until is_active is
false (the UI receives the same steps live via Socket.IO).
Cancel: sets cancel_requested; the run stops after the edition it is
working on, remaining editions become skipped. A run that has already
finished stays unchanged and the response is its final state.
Resume: a run can also end without a cancel, for example when the server
stops the process during a deploy or restart; it is then failed
(summary.error e.g. "Worker beendet"). For failed and cancelled runs
open_conference_ids lists the editions that are still open: ended with an
error (error) or skipped (skipped), but not deleted. For every other
status the list is empty. POST /refresh-runs/{id}/resume starts a new
run over exactly these editions with the same scope and answers 202 with
{"success": true, "run": {…}}. The old run stays unchanged as history. If
the run is not failed or cancelled, or another run of the group is
active, you get 409; if nothing is left open, 400. Runs of other
groups answer 404, and a viewer gets 403.
Examples¶
export LLARS=https://llars.example.org
export K=llars_… # personal key with conference:write
# Your research groups
curl -s -H "X-API-Key: $K" "$LLARS/api/v1/conference-manager/research-groups"
# Create a series
curl -s -X POST -H "X-API-Key: $K" -H "Content-Type: application/json" \
-d '{"group_id":1,"name":"Empirical Methods in NLP","acronym":"EMNLP","core_ranking":"A*"}' \
"$LLARS/api/v1/conference-manager/series"
# Estimated edition (windows only)
curl -s -X POST -H "X-API-Key: $K" -H "Content-Type: application/json" \
-d '{
"group_id": 1, "series_id": 3, "name": "EMNLP 2027", "acronym": "EMNLP", "year": 2027,
"submission_window_start": "2027-05-10", "submission_window_end": "2027-05-25",
"notification_window_start": "2027-08-15", "notification_window_end": "2027-08-31",
"conference_window_start": "2027-10-25", "conference_window_end": "2027-11-15",
"estimation_basis": "derived from 2023-2026", "estimation_confidence": "medium"
}' \
"$LLARS/api/v1/conference-manager/conferences"
# The date is announced: set the exact value (the window stays as history)
curl -s -X PATCH -H "X-API-Key: $K" -H "Content-Type: application/json" \
-d '{"submission_deadline":"2027-05-19T23:59:00"}' \
"$LLARS/api/v1/conference-manager/conferences/12"
# All still-estimated editions from January 2027 on
curl -s -H "X-API-Key: $K" \
"$LLARS/api/v1/conference-manager/conferences?group_id=1&estimated=true&from=2027-01-01"
# Paper with DOI, arXiv and authors
curl -s -X POST -H "X-API-Key: $K" -H "Content-Type: application/json" \
-d '{
"group_id": 1, "title": "Our Paper", "status": "published", "conference_id": 12,
"doi": "doi:10.18653/v1/2024.acl-long.1", "arxiv_url": "arXiv:2401.12345",
"authors": [{"username": "alice", "is_corresponding": true}, {"external_name": "Jane Doe"}]
}' \
"$LLARS/api/v1/conference-manager/papers"
# Append a submission
curl -s -X POST -H "X-API-Key: $K" -H "Content-Type: application/json" \
-d '{"conference_id":12,"status":"submitted","submitted_at":"2027-05-19T20:00:00"}' \
"$LLARS/api/v1/conference-manager/papers/5/submissions"
# Start, watch and cancel the AI refresh
curl -s -X POST -H "X-API-Key: $K" -H "Content-Type: application/json" \
-d '{"group_id":1}' "$LLARS/api/v1/conference-manager/conferences/refresh"
curl -s -H "X-API-Key: $K" "$LLARS/api/v1/conference-manager/refresh-runs/7"
curl -s -X POST -H "X-API-Key: $K" "$LLARS/api/v1/conference-manager/refresh-runs/7/cancel"
# … and later resume the open editions in a new run
curl -s -X POST -H "X-API-Key: $K" "$LLARS/api/v1/conference-manager/refresh-runs/7/resume"
Security / hardening¶
- IDOR masking (M1): objects of foreign groups answer with 404, not 403 —
no ID-existence leaks. Cross references (
series_id,conference_id) may not leave the group. - Scope ∧ permission: a key only works while its account holds the feature permission.
- Strict schemas: unknown fields, invalid dates and non-http(s) links are a 400 instead of being dropped silently.
- Size caps:
keywords≤ 50,authors≤ 50,conference_ids≤ 200,limit≤ 200. - Conflicts (duplicates, active AI run) are 409 instead of 500.
Out of scope¶
- Creating or changing research groups and memberships (UI only)
- Moving records between groups
- Deleting AI runs or scheduling them (the AI runs on request only)