Important
All API requests must include an Authorization header in the following format: Authorization: Bearer <BEARER_TOKEN>
Rate Limit: Configurable via RATE_LIMIT environment variable (default 5) requests per minute per IP address All endpoints return JSON responses with appropriate HTTP status codes
Endpoint: /think-root/api/manual-generate/
Method: POST
Description: This endpoint is used to manually generate description for a provided repository URL, and add it to the database. Supports multilingual text generation.
Curl Example:
curl -X POST \
'http://localhost:8080/think-root/api/manual-generate/' \
-H 'Authorization: Bearer <BEARER_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://github.com/example/repo",
"llm_output_language": "en,uk,fr",
"llm_provider": "mistral_api",
"llm_config": {
"model": "mistral-small-latest"
}
}'Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
url |
string | Yes | GitHub repository URL. Supports multiple whitespace-separated URLs to process in a single request. |
llm_output_language |
string | No | Comma-separated language codes (e.g., "en,uk,fr"). Default: "uk". |
llm_provider |
string | No | LLM provider name. Values: mistral_api (default), openai, openrouter, chutes. If omitted, defaults to mistral_api. |
llm_config |
object | Yes* | Provider configuration. Required for all providers to specify at least the model. See llm_config Structure section below. |
use_direct_url |
boolean | No | If true, the URL string is used directly as LLM input instead of README content. |
* llm_config is required for all providers to specify the model.
llm_config Structure:
The llm_config object is passed to the LLM provider's chat completion API. Common parameters:
model: (Required) The model ID (e.g.,mistral-small-latest,gpt-4o,google/gemini-2.0-flash-exp:free,moonshotai/Kimi-K2-Instruct-0905).temperature: (Optional) Sampling temperature (0.0 to 1.0).max_tokens: (Optional) Maximum tokens to generate.top_p: (Optional) Nucleus sampling probability.messages: (Optional) Array of message objects to specify a custom prompt. See below.
Custom Prompt:
You can provide a custom system prompt via the messages array. The server will append its multilingual instructions to your prompt:
"llm_config": {
"model": "mistral-small-latest",
"messages": [
{
"role": "system",
"content": "Your custom prompt here. Describe repos in a fun, engaging way."
}
]
}If you don't provide a messages array, the server creates one with default multilingual instructions.
Request Examples:
- Basic request with Mistral (default provider):
{
"url": "https://github.com/example/repo",
"llm_config": {
"model": "mistral-small-latest"
}
}- Request with Chutes provider:
{
"url": "https://github.com/example/repo",
"llm_provider": "chutes",
"llm_output_language": "en",
"llm_config": {
"model": "moonshotai/Kimi-K2-Instruct-0905"
}
}- Multilingual request with OpenRouter:
{
"url": "https://github.com/example/repo",
"llm_output_language": "en,uk,fr",
"llm_provider": "openrouter",
"llm_config": {
"model": "google/gemini-2.0-flash-exp:free"
}
}Status Codes:
- 200: Success
- 400: Invalid request
- 401: Unauthorized
Response Fields:
| Field | Type | Description |
|---|---|---|
status |
string | Response status: ok (all succeeded), partial (some failed), error (all failed) |
added |
array[string] | URLs of repositories successfully added to the database |
dont_added |
array[string] | URLs of repositories that failed to process |
error_message |
string (optional) | General error message when failures occur |
error_details |
object (optional) | Detailed error information for each failed repository (URL → ErrorDetail mapping) |
ErrorDetail Object Structure:
| Field | Type | Description |
|---|---|---|
type |
string | Error type: invalid_url, already_exists, no_readme, insufficient_content, non_english_readme, low_quality or processing_error |
message |
string | Human-readable error message describing what went wrong |
Error Types:
invalid_url: The value is not a GitHub repository URL. Any GitHub URL form is accepted (missing scheme,http://,www., tracking query parameters, a.gitsuffix, a deep path such as/tree/main/src) and normalized tohttps://github.com/<owner>/<repo>; the reported key is the original inputalready_exists: Repository already exists in the databaseno_readme: README file not found in the repository (not reported whenuse_direct_urlis enabled — the README is then optional)insufficient_content: README has less meaningful content thanREADME_MIN_CONTENT_LENGTH(default 150); the message includes the measured lengthnon_english_readme: README is not predominantly written in a Latin script — more thanREADME_MAX_NON_LATIN_PERCENT(default 20) percent of its letters are non-Latin; the message includes the measured sharelow_quality: The generated description was rejected as empty, shorter thanMIN_DESCRIPTION_LENGTH(default 40) or as an LLM refusal; the message includes the exact reasonprocessing_error: LLM processing or database insertion failed
Response Examples:
Success:
{
"status": "ok",
"added": ["https://github.com/example/repo"],
"dont_added": []
}Single error with details:
{
"status": "error",
"added": [],
"dont_added": ["https://github.com/example/repo"],
"error_message": "All repositories failed to process",
"error_details": {
"https://github.com/example/repo": {
"type": "already_exists",
"message": "Repository already exists in database"
}
}
}Multiple errors with different types:
{
"status": "partial",
"added": ["https://github.com/example/success"],
"dont_added": [
"https://github.com/example/existing",
"https://github.com/example/no-docs",
"https://github.com/example/failed"
],
"error_message": "3 repositories failed to process",
"error_details": {
"https://github.com/example/existing": {
"type": "already_exists",
"message": "Repository already exists in database"
},
"https://github.com/example/no-docs": {
"type": "no_readme",
"message": "README file not found in repository"
},
"https://github.com/example/failed": {
"type": "processing_error",
"message": "LLM processing failed"
}
}
}Migration Notes:
The error_details field is backward compatible:
- Existing clients that don't expect this field will continue to work without changes
- The field is optional (
omitempty) and only present when errors occur - All existing response fields (
status,added,dont_added,error_message) remain unchanged - New clients can use
error_detailsto provide more specific error feedback to users
Endpoint: /think-root/api/auto-generate/
Method: POST
Description: This endpoint is used to automatically parse trending repositories and generate description based on certain parameters. It also adds the generated posts to the database. Supports multilingual text generation. Supports multiple data sources (GitHub, OssInsight).
Curl Example:
curl -X POST \
'http://localhost:8080/think-root/api/auto-generate/' \
-H 'Authorization: Bearer <BEARER_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"max_repos": 5,
"resource": "github",
"since": "weekly",
"spoken_language_code": "en",
"llm_output_language": "en,uk,fr",
"llm_provider": "mistral_api",
"llm_config": {
"model": "mistral-small-latest"
}
}'Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
max_repos |
integer | Yes | Maximum number of repositories to process. Must be > 0. |
resource |
string | No | Data source. Values: github (default), ossinsight. |
since |
string | No | For GitHub resource: Time period for trending repos (daily, weekly, monthly). |
spoken_language_code |
string | No | For GitHub resource: Spoken language filter for GitHub Trending. |
period |
string | No | For OssInsight resource: Time period (past_24_hours, past_week, past_month, past_3_months). Default: past_24_hours. |
language |
string | No | For OssInsight resource: Programming language filter (e.g., Python, All). Default: All. |
llm_output_language |
string | No | Comma-separated language codes for output (e.g., en,uk,fr). Default: uk. |
llm_provider |
string | No | LLM provider name. Values: mistral_api (default), openai, openrouter, chutes. If omitted, defaults to mistral_api. |
llm_config |
object | Yes* | Provider configuration. Required for all providers to specify at least the model. |
use_direct_url |
boolean | No | If true, the repository URL string is used directly as LLM input instead of README content. |
* llm_config is required for all providers to specify the model.
llm_config Structure:
The llm_config object is passed as the JSON body to the chosen LLM provider's chat completion API (e.g., OpenAI, Mistral, OpenRouter). Common parameters include:
model: (Required) The ID of the model to use (e.g.,gpt-4o,mistral-large-latest,google/gemini-2.0-flash-exp:free).temperature: (Optional) Sampling temperature (0.0 to 1.0). Higher values mean more random output.max_tokens: (Optional) The maximum number of tokens to generate.top_p: (Optional) Nucleus sampling probability.stream: (Optional) Boolean to stream responses (usuallyfalsefor this API).messages: (Optional) Array of message objects to specify a custom prompt. See below.
Custom Prompt:
You can provide a custom system prompt via the messages array. The server will append its multilingual instructions to your custom prompt:
"llm_config": {
"model": "google/gemini-2.0-flash-exp:free",
"messages": [
{
"role": "system",
"content": "Your custom prompt here. Be concise and creative."
}
]
}If you don't provide a messages array, the server creates one with default multilingual instructions based on llm_output_language.
Request Examples:
- Basic request (GitHub source):
{
"max_repos": 5,
"resource": "github",
"since": "weekly",
"spoken_language_code": "en",
"llm_provider": "mistral_api",
"llm_config": {
"model": "mistral-tiny"
}
}- Request with Chutes.ai provider:
{
"max_repos": 3,
"since": "daily",
"spoken_language_code": "en",
"llm_provider": "chutes",
"llm_output_language": "uk,en",
"llm_config": {
"model": "moonshotai/Kimi-K2-Instruct-0905",
"temperature": 0.5,
"max_tokens": 1024
}
}- Multilingual request with OpenRouter:
{
"max_repos": 5,
"since": "weekly",
"spoken_language_code": "en",
"llm_output_language": "en,uk,fr",
"llm_provider": "openrouter",
"llm_config": {
"model": "google/gemini-2.0-flash-exp:free"
}
}- Request using OssInsight source:
{
"max_repos": 10,
"resource": "ossinsight",
"period": "past_month",
"language": "Python",
"llm_provider": "mistral_api",
"llm_config": {
"model": "mistral-small"
}
}Response Example:
{
"status": "ok",
"added": ["https://github.com/example/repo1", "https://github.com/example/repo2"],
"dont_added": ["https://github.com/example/repo3"]
}Endpoint: /think-root/api/get-repository/
Method: POST
Description: This endpoint retrieves a list of repositories based on the provided limit, posted status, and sorting preferences. Results can be sorted by different fields and directions, with special handling for null values in publication dates. By default, if text_language is omitted, the endpoint returns the raw multilingual text exactly as stored, e.g., "===(en)text===(uk)текст===". If text_language is provided (e.g., "en" or "uk"), the endpoint returns only that language’s text. If the requested language is not available, the request still succeeds and falls back to the Ukrainian text, or to the first language stored.
Curl Example:
curl -X POST \
'http://localhost:8080/think-root/api/get-repository/' \
-H 'Authorization: Bearer <BEARER_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"limit": 1,
"posted": false,
"sort_by": "date_added",
"sort_order": "DESC",
"text_language": "uk"
}'Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
integer | No | Maximum number of repositories to return. Set to 0 to either return all records (if page and page_size are not specified) or use pagination mode (if page or page_size are specified). |
posted |
boolean | No | Filter repositories by posted status. If not specified and limit is 0, returns all records regardless of posted status. |
sort_by |
string | No | Field to sort results by. Valid values:id, date_added, date_posted, publication_queue. Default: date_added for unposted repositories, date_posted for posted repositories. When sorting by date_posted, repositories without a publication date (null) will be displayed according to the sorting order. |
sort_order |
string | No | Order of sorting. Valid values:ASC (ascending), DESC (descending). Default: DESC. |
page |
integer | No | Page number for pagination (1-based). If not specified along with page_size and limit is 0, all records will be returned without pagination. |
page_size |
integer | No | Number of items per page. If not specified along with page and limit is 0, all records will be returned without pagination. |
text_language |
string | No | Optional. When omitted, raw multilingual text is returned in the original format, for example "===(en)text===(uk)text===". When provided (e.g., "en", "uk"), the API extracts and returns only the specified language’s text. |
id |
integer | No | Address a single repository directly by id. Mutually exclusive with url. When set, sorting, limit and pagination are ignored and the response carries exactly that repository. |
url |
string | No | Address a single repository directly by url. Mutually exclusive with id. Same semantics as id. |
Addressing a single repository:
id and url exist for callers that already know which repository they want — for example content-maestro re-sending a publication that failed for one social connector. Because the item is fetched by identity rather than pulled from the queue, it is returned even when it is already posted.
text_language applies exactly as it does in queue mode, including its fallback: if the repository has no text in the requested language, the response is still 200 and carries the Ukrainian text, or the first language stored, with no indication that a substitution happened. A caller that must publish in one specific language has to compare the text itself.
curl -X POST \
'http://localhost:8080/think-root/api/get-repository/' \
-H 'Authorization: Bearer <BEARER_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://github.com/resemble-ai/chatterbox",
"text_language": "en"
}'Responses: 200 with a single-item payload (page, page_size, total_pages, total_items are all 1; all/posted/unposted still report global counts), 400 when both id and url are given or the identifier is empty/non-positive, 404 when no repository matches.
Request Examples:
- Get all records without pagination:
{
"limit": 0,
"posted": null,
"sort_by": "date_added",
"sort_order": "DESC"
}- Get records with pagination:
{
"limit": 0,
"posted": null,
"sort_by": "date_added",
"sort_order": "DESC",
"page": 1,
"page_size": 10
}- Get limited number of records:
{
"limit": 5,
"posted": true,
"sort_by": "date_posted",
"sort_order": "DESC"
}- Example: Specifying text_language: 'uk' returns only the Ukrainian text
{
"limit": 10,
"text_language": "uk"
}- ** Get English text with pagination:**
{
"limit": 0,
"page": 1,
"page_size": 5,
"text_language": "en"
}- Example: Without text_language (raw multilingual text)
{
"limit": 10
}Returns raw multilingual text segments in the original format, e.g., "===(en)text===(uk)text===".
Pagination Details:
- When
limitis 0:- If neither
pagenorpage_sizeare specified, returns all matching records without pagination. In this case,page=0,page_size=0,total_pages=1, andtotal_itemsequals the count of all matching records. - If either
pageorpage_sizeare specified, pagination mode is used.
- If neither
- When
limit> 0:- Pagination mode is used. If
page< 1, it defaults to 1. Ifpage_size< 1, it defaults to 10.
- Pagination mode is used. If
- Effective page size. A page holds
limitrows whenlimit> 0, andpage_sizerows otherwise. The offset andtotal_pagesare both derived from that effective size, andpage_sizein the response echoes it. So{"limit": 4, "page_size": 2}returns 4 rows per page and reportspage_size: 4— sending both no longer makes consecutive pages overlap. - Response always includes:
page: Current page number (0 when returning all records without pagination; otherwise the active page number).page_size: Number of items per page (0 when returning all records without pagination; otherwise the effective page size described above).total_pages: Total number of pages (1 when returning all records without pagination).total_items: Total number of items matching the query.
Sorting Behavior:
- When sorting by
date_posted:- If
sort_order=ASC: entries with null values are shown first, followed by dates in ascending order - If
sort_order=DESC: entries with dates are shown in descending order first, followed by those with null values
- If
- When sorting by
publication_queue: promoted unposted repositories are shown first by newest promotion, followed by normal unposted repositories ordered by oldestdate_added - When sorting by
date_addedorid: standard ascending or descending sort - If
sort_byis not specified,date_postedis used for posted=true anddate_addedfor posted=false - If
sort_orderis not specified,DESCis used as default
Response Example:
{
"status": "ok",
"message": "Repositories fetched successfully",
"data": {
"all": 50,
"posted": 20,
"unposted": 30,
"items": [
{
"id": 1,
"posted": false,
"url": "https://github.com/example/repo",
"text": "Repository description here.",
"date_added": "2025-03-20T15:30:45Z",
"date_posted": null,
"publish_priority": null
}
],
"page": 1,
"page_size": 10,
"total_pages": 5,
"total_items": 50
}
}Additional Response Example (raw multilingual text):
{
"text": "===(en)An open-source project===(uk)Відкритий проект===",
"... other fields ...": "..."
}Note: This indicates raw multilingual text as stored.
Endpoint: /think-root/api/update-posted/
Method: PATCH
Description: This endpoint updates the posted status of a repository identified by its URL. Note: when the URL does not exist, the current implementation returns 500 with a generic error rather than 404. Setting posted=true sets date_posted to current time; posted=false clears date_posted. Any status change clears publish_priority per database.UpdatePostedStatusByURL().
Curl Example:
curl -X PATCH \
'http://localhost:8080/think-root/api/update-posted/' \
-H 'Authorization: Bearer <BEARER_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://github.com/example/repo",
"posted": true
}'Request Example:
{
"url": "https://github.com/example/repo",
"posted": true
}Response Example:
{
"status": "ok",
"message": "Posted status updated successfully"
}Endpoint: /think-root/api/promote-repository/
Method: PATCH
Description: Promotes an unposted repository to the front of the publication queue without changing its historical date_added. The newest promotion wins. Posted repositories cannot be promoted.
Curl Example:
curl -X PATCH \
'http://localhost:8080/think-root/api/promote-repository/' \
-H 'Authorization: Bearer <BEARER_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"id": 123
}'Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | No | Repository ID. Provide either id or url. |
url |
string | No | Repository URL. Provide either id or url. |
Response Example:
{
"status": "ok",
"message": "Repository promoted to publish next",
"data": {
"id": 123,
"posted": false,
"url": "https://github.com/example/repo",
"text": "Repository description here.",
"date_added": "2025-03-20T15:30:45Z",
"date_posted": null,
"publish_priority": 7
}
}Error Responses:
400: invalid request body or identifier404: repository not found409: repository is already posted
Endpoint: /think-root/api/update-repository-text/
Method: PATCH
Description: Updates the repository text with two modes:
- Full replace when
text_languageis omitted. - Strict language-specific update when
text_languageis provided. If the specified language does not exist in the existing multilingual content, returns 422 Unprocessable Entity.
Request Schema:
- Exactly one of
idorurlmust be provided textis requiredtext_languageis optional (language code). When provided, triggers language-specific update.- Language code validation is performed by language.ValidateLanguageCodes()
Curl and JSON Examples:
- Full replace (no text_language):
curl -X PATCH \
'http://localhost:8080/think-root/api/update-repository-text/' \
-H 'Authorization: Bearer <BEARER_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"id": 1172,
"text": "Updated text content via ID"
}'Result in DB: "Updated text content via ID"
Response includes available_languages (for plain: ["uk"]) and omits updated_language.
- Language update on existing multilingual:
curl -X PATCH \
'http://localhost:8080/think-root/api/update-repository-text/' \
-H 'Authorization: Bearer <BEARER_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"id": 1172,
"text": "Updated text content via ID",
"text_language": "en"
}'Result in DB: only the en segment updated; other segments unchanged.
- Language update on plain existing text:
curl -X PATCH \
'http://localhost:8080/think-root/api/update-repository-text/' \
-H 'Authorization: Bearer <BEARER_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"id": 1172,
"text": "тут якийсь текст",
"text_language": "uk"
}'Result in DB: "===(uk)тут якийсь текст==="
- Error when language missing in existing multilingual:
curl -X PATCH \
'http://localhost:8080/think-root/api/update-repository-text/' \
-H 'Authorization: Bearer <BEARER_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"id": 1172,
"text": "KURWA! Ja perdoly jjajajajaj.",
"text_language": "pl"
}'Response: 422 Unprocessable Entity with message language 'pl' not found in existing content.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | No* | Repository ID (positive integer) |
url |
string | No* | Repository URL (non-empty string) |
text |
string | Yes | New text content (1-1000 characters, valid UTF-8) |
text_language |
string | No | Optional language code. When provided, performs a language-specific update (validated). |
*Exactly one of id or url must be provided.
Validation Rules:
- Exactly one identifier (
idorurl) must be provided textis required and non-emptytextlength ≤ 1000 characterstextmust be valid UTF-8text_languagevalidated via language.ValidateLanguageCodes()
Response Fields:
statusandmessagedata.id,data.url,data.text(final text stored)data.updated_languagepresent only whentext_languageis provideddata.available_languagesvia multilingual.GetAvailableLanguages()data.updated_at
Success Response Examples:
Full replace (plain text):
{
"status": "ok",
"message": "Repository text updated successfully",
"data": {
"id": 1172,
"url": "https://github.com/example/repo",
"text": "Updated text content via ID",
"available_languages": ["uk"],
"updated_at": "2025-06-22T15:00:00Z"
}
}Language-specific update:
{
"status": "ok",
"message": "Repository text updated successfully",
"data": {
"id": 1172,
"url": "https://github.com/example/repo",
"text": "Updated text content via ID",
"updated_language": "en",
"available_languages": ["en", "uk"],
"updated_at": "2025-06-22T15:00:00Z"
}
}Error Response Example (missing language in existing multilingual):
{
"status": "error",
"message": "language 'pl' not found in existing content"
}Endpoint: /think-root/api/delete-repository/
Method: DELETE
Description: This endpoint deletes a repository from the database. The repository can be identified by either its unique ID or URL.
Curl Examples:
Delete by ID:
curl -X DELETE \
'http://localhost:8080/think-root/api/delete-repository/' \
-H 'Authorization: Bearer <BEARER_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"id": 123
}'Delete by URL:
curl -X DELETE \
'http://localhost:8080/think-root/api/delete-repository/' \
-H 'Authorization: Bearer <BEARER_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://github.com/example/repo"
}'Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | No* | Repository ID (positive integer) |
url |
string | No* | Repository URL (non-empty string) |
*Either id or url must be provided, but not both.
Request Examples:
- Delete by ID:
{
"id": 123
}- Delete by URL:
{
"url": "https://github.com/example/awesome-project"
}Validation Rules:
- Exactly one identifier (
idorurl) must be provided - ID must be a positive integer if provided
- URL must be a non-empty string if provided
Status Codes:
- 200: Success - Repository deleted
- 400: Bad Request - Validation errors
- 401: Unauthorized - Invalid or missing Bearer token
- 405: Method Not Allowed - Wrong HTTP method
- 500: Internal Server Error - Database or server error (including when URL not found in update-posted)
Success Response Example:
{
"status": "ok",
"message": "Repository deleted successfully"
}Error Response Examples:
{
"status": "error",
"message": "Either id or url must be provided"
}{
"status": "error",
"message": "Provide either id or url, not both"
}{
"status": "error",
"message": "repository with ID 123 not found"
}{
"status": "error",
"message": "repository with URL https://github.com/example/repo not found"
}Archiving moves a published repository out of github_repositories into the append-only archived_repositories table, keeping its original id, description, date_added and date_posted, and stamping date_archived.
Consequences of the move:
- The repository disappears from
/api/get-repository/and its URL becomes free again, so the very same repository can be collected by/api/manual-generate/or/api/auto-generate/and published a second time. - URLs are not unique in the archive: the same repository may appear there several times, once per publication cycle.
- There is no way back: a repository cannot be restored from the archive, and archived rows cannot be deleted. This is deliberate — it keeps the archive free of ambiguity when a repository has been published more than once.
- Only published repositories (
posted = 1) can be archived. Unpublished ones can only be removed with/api/delete-repository/.
Endpoint: /think-root/api/archive-repository/
Method: POST
Description: Archives one or more published repositories, identified either by ids or by urls. Identifiers that cannot be archived (not found, not published, repeated in the same request) are reported in failed and do not abort the rest of the batch, so the response is 200 even when some or all identifiers were rejected. Implemented by database.ArchiveRepositories().
Curl Examples:
Archive by ids:
curl -X POST \
'http://localhost:8080/think-root/api/archive-repository/' \
-H 'Authorization: Bearer <BEARER_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"ids": [123, 124]
}'Archive by urls:
curl -X POST \
'http://localhost:8080/think-root/api/archive-repository/' \
-H 'Authorization: Bearer <BEARER_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"urls": ["https://github.com/example/repo"]
}'Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
ids |
array of integer | No* | Repository ids (positive integers) |
urls |
array of string | No* | Repository urls (non-empty strings) |
*Exactly one of ids or urls must be provided, non-empty, with at most 100 entries.
Validation Rules:
- Exactly one of
idsorurlsmust be provided and must not be empty - Every id must be a positive integer
- Every url must be a non-empty string
- At most 100 identifiers per request
Failure Reasons (per identifier, inside failed):
| Reason | Meaning |
|---|---|
not_found |
No repository with this id/url exists |
not_posted |
The repository has not been published yet |
already_processed |
The identifier was repeated in the same request |
Status Codes:
- 200: Success - see
archivedandfailedfor per-identifier results - 400: Bad Request - Validation errors
- 401: Unauthorized - Invalid or missing Bearer token
- 405: Method Not Allowed - Wrong HTTP method
- 500: Internal Server Error - Database or server error
Success Response Example:
{
"status": "ok",
"message": "Archived 1 of 2 repositories",
"data": {
"archived": [
{
"archive_id": 11,
"id": 123,
"url": "https://github.com/example/repo",
"date_added": "2025-03-20T15:30:45Z",
"date_posted": "2025-03-25T09:00:00Z",
"date_archived": "2025-05-01T12:00:00Z"
}
],
"failed": [
{
"identifier": "124",
"reason": "not_posted",
"message": "only published repositories can be archived"
}
]
}
}Error Response Examples:
{
"status": "error",
"message": "Either ids or urls must be provided"
}{
"status": "error",
"message": "Provide either ids or urls, not both"
}{
"status": "error",
"message": "A maximum of 100 ids can be archived per request"
}Endpoint: /think-root/api/archive-old-repositories/
Method: POST
Description: Archives every published repository whose publication date is older than days days. The age is measured by date_posted, not by date_added; repositories without a publication date are never touched. Use dry_run to preview the affected repositories without changing anything. Implemented by database.ArchiveRepositoriesOlderThan().
Curl Example:
curl -X POST \
'http://localhost:8080/think-root/api/archive-old-repositories/' \
-H 'Authorization: Bearer <BEARER_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"days": 30,
"dry_run": false
}'Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
days |
integer | Yes | Archive repositories published more than this many days ago. Must be >= 1. |
dry_run |
boolean | No | When true, returns the matching repositories without archiving them. Default false. |
Validation Rules:
daysmust be a positive integer;0or a missing value is rejected so nothing can be wiped by accident
Status Codes:
- 200: Success - Repositories archived (or previewed)
- 400: Bad Request - Validation errors
- 401: Unauthorized - Invalid or missing Bearer token
- 405: Method Not Allowed - Wrong HTTP method
- 500: Internal Server Error - Database or server error
Success Response Example:
{
"status": "ok",
"message": "Archived 2 repositories published more than 30 days ago",
"data": {
"archived_count": 2,
"dry_run": false,
"archived": [
{
"archive_id": 11,
"id": 123,
"url": "https://github.com/example/repo",
"date_added": "2025-01-10T15:30:45Z",
"date_posted": "2025-01-15T09:00:00Z",
"date_archived": "2025-05-01T12:00:00Z"
}
]
}
}On a dry run archive_id is 0 and date_archived is null, because nothing was written.
Error Response Example:
{
"status": "error",
"message": "days must be a positive integer"
}Endpoint: /think-root/api/get-archived-repositories/
Method: POST
Description: Returns a paginated, filtered and sorted view of the archive. Supports substring search over url and text, plus independent ranges over all three dates (date_added, date_posted, date_archived). Pagination behaves exactly like /api/get-repository/. Implemented by database.GetArchivedRepositories().
Curl Example:
curl -X POST \
'http://localhost:8080/think-root/api/get-archived-repositories/' \
-H 'Authorization: Bearer <BEARER_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"page": 1,
"page_size": 10,
"url": "example",
"date_archived_from": "2025-01-01",
"sort_by": "date_archived",
"sort_order": "desc",
"text_language": "uk"
}'Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
integer | No | Maximum number of rows to return. Same semantics as in /api/get-repository/. |
page |
integer | No | Page number for pagination (1-based). |
page_size |
integer | No | Number of items per page. Defaults to 10 once pagination is active. |
sort_by |
string | No | Sort field. Valid values:date_archived, date_posted, date_added, id. Default: date_archived. |
sort_order |
string | No | asc or desc (case-insensitive). Default: desc. |
url |
string | No | Case-insensitive substring match on the repository url, Cyrillic included. % and _ are matched literally. |
text |
string | No | Case-insensitive substring match on the stored description, Cyrillic included. |
date_added_from |
string | No | Lower bound (inclusive) on date_added. RFC3339 timestamp or YYYY-MM-DD. |
date_added_to |
string | No | Upper bound (exclusive) on date_added. A bare YYYY-MM-DD covers the whole day. |
date_posted_from |
string | No | Lower bound (inclusive) on date_posted. |
date_posted_to |
string | No | Upper bound (exclusive) on date_posted. |
date_archived_from |
string | No | Lower bound (inclusive) on date_archived. |
date_archived_to |
string | No | Upper bound (exclusive) on date_archived. |
text_language |
string | No | When omitted, the raw multilingual text is returned as stored. When provided (e.g. en, uk), only that language is returned. |
All filters are combined with AND.
Validation Rules:
- Dates must be an RFC3339 timestamp (
2025-05-01T12:00:00Z) or a bare date (2025-05-01) sort_bymust be one ofdate_archived,date_posted,date_added,idsort_ordermust beascordesclimit,pageandpage_sizemust not be negativetext_languagemust be a single valid language code (comma-separated lists are rejected)
Sorting Behavior:
- Rows whose sort column is
NULL(possible fordate_posted/date_added) are always placed last, in bothascanddesc.date_archivedis never null. - Ties are broken by
idin the same direction as the requested order.
Pagination Details:
- Identical to
/api/get-repository/: when neitherpage,page_sizenorlimitis set, all matching rows are returned withpage=0,page_size=0,total_pages=1. The same effective page size rule applies — a page holdslimitrows whenlimit> 0, and the offset,total_pagesand the echoedpage_sizeall follow it. allis the total number of rows in the archive, ignoring filters;total_itemsis the number of rows matching the current filter.
Status Codes:
- 200: Success - Archived repositories fetched
- 400: Bad Request - Validation errors
- 401: Unauthorized - Invalid or missing Bearer token
- 405: Method Not Allowed - Wrong HTTP method
- 500: Internal Server Error - Database or server error
Success Response Example:
{
"status": "ok",
"message": "Archived repositories fetched successfully",
"data": {
"all": 120,
"items": [
{
"id": 11,
"original_id": 123,
"url": "https://github.com/example/repo",
"text": "Repository description here.",
"date_added": "2025-03-20T15:30:45Z",
"date_posted": "2025-03-25T09:00:00Z",
"date_archived": "2025-05-01T12:00:00Z"
}
],
"page": 1,
"page_size": 10,
"total_pages": 12,
"total_items": 120
}
}id is the archive row id; original_id is the id the repository had in github_repositories before it was archived (it may be reused by a later, unrelated repository).
Error Response Examples:
{
"status": "error",
"message": "Invalid date_archived_from: must be an RFC3339 timestamp or a YYYY-MM-DD date"
}{
"status": "error",
"message": "sort_by must be one of: date_archived, date_posted, date_added, id"
}