{
    "openapi": "3.1.0",
    "info": {
        "title": "TickTrac",
        "version": "1.0.0",
        "description": "Files finished time entries into a TickTrac account. Up to 100 entries per call, each at most 24 hours long and in the past; 120 calls per 1 hour per account. Re-sending the same ref is safe: it is filed once."
    },
    "servers": [
        {
            "url": "https://ticktrac.com"
        }
    ],
    "security": [
        {
            "bearerAuth": []
        }
    ],
    "paths": {
        "/api/v1/entries": {
            "post": {
                "operationId": "fileEntries",
                "summary": "File finished time entries",
                "description": "Files one or more finished entries. Times are ISO-8601 with a timezone offset. Unknown clients and categories are created. An agent-only token files agent time and cannot write \u0022source\u0022: \u0022human\u0022.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "entries"
                                ],
                                "properties": {
                                    "entries": {
                                        "type": "array",
                                        "minItems": 1,
                                        "maxItems": 100,
                                        "items": {
                                            "$ref": "#/components/schemas/Entry"
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Every entry was filed (or was already filed).",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Result"
                                }
                            }
                        }
                    },
                    "207": {
                        "description": "Some entries were filed; the rest are in errors.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Result"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "No entry was filed; errors says why for each.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Result"
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "The body is not {\u0022entries\u0022: [...]}, is empty, or holds too many.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "No token, or a revoked or unknown one.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate-limited. Wait Retry-After seconds and send the same call again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "bearerAuth": {
                "type": "http",
                "scheme": "bearer",
                "description": "The 64-character token from Settings → API tokens."
            }
        },
        "schemas": {
            "Entry": {
                "type": "object",
                "required": [
                    "ref",
                    "name",
                    "started_at",
                    "ended_at"
                ],
                "properties": {
                    "ref": {
                        "type": "string",
                        "maxLength": 128,
                        "description": "Your own stable id for this entry. Re-sending the same ref files nothing new."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "One line on what was done."
                    },
                    "client": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Who the work is for. A new name creates the client."
                    },
                    "categories": {
                        "type": "array",
                        "items": {
                            "type": "string",
                            "maxLength": 255
                        },
                        "description": "What kind of work it was. New names are created."
                    },
                    "tags": {
                        "type": "array",
                        "items": {
                            "type": "string",
                            "maxLength": 255
                        },
                        "description": "Free labels."
                    },
                    "started_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "ISO-8601 with an offset, e.g. 2026-10-06T09:00:00+02:00."
                    },
                    "ended_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "ISO-8601 with an offset, at or before now, at most 24 hours after started_at."
                    },
                    "source": {
                        "type": "string",
                        "enum": [
                            "human",
                            "agent"
                        ],
                        "description": "Whose time this is. Defaults to human on a full token and to agent on an agent-only one."
                    },
                    "agent": {
                        "type": "string",
                        "maxLength": 64,
                        "description": "Agent entries only: the agent\u0027s name, e.g. \u0022ChatGPT\u0022."
                    },
                    "human_equivalent_hours": {
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "maximum": 1000,
                        "description": "Agent entries only: how long a competent person would have needed, in decimal hours."
                    }
                }
            },
            "Result": {
                "type": "object",
                "required": [
                    "results",
                    "errors"
                ],
                "properties": {
                    "results": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "ref": {
                                    "type": "string"
                                },
                                "id": {
                                    "type": "integer"
                                },
                                "created": {
                                    "type": "boolean",
                                    "description": "False when this ref was already filed."
                                }
                            }
                        }
                    },
                    "errors": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "index": {
                                    "type": "integer"
                                },
                                "ref": {
                                    "type": [
                                        "string",
                                        "null"
                                    ]
                                },
                                "message": {
                                    "type": "string"
                                }
                            }
                        }
                    }
                }
            },
            "Error": {
                "type": "object",
                "properties": {
                    "error": {
                        "type": "string"
                    },
                    "message": {
                        "type": "string"
                    }
                }
            }
        }
    }
}