diff --git a/src/Api/Public/Controllers/EventsController.cs b/src/Api/Public/Controllers/EventsController.cs
new file mode 100644
index 0000000000..9e3b7727e1
--- /dev/null
+++ b/src/Api/Public/Controllers/EventsController.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Linq;
+using System.Net;
+using System.Threading.Tasks;
+using Bit.Core;
+using Bit.Core.Models.Api.Public;
+using Bit.Core.Models.Data;
+using Bit.Core.Repositories;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Bit.Api.Public.Controllers
+{
+ [Route("public/events")]
+ [Authorize("Organization")]
+ public class EventsController : Controller
+ {
+ private readonly IEventRepository _eventRepository;
+ private readonly ICipherRepository _cipherRepository;
+ private readonly CurrentContext _currentContext;
+
+ public EventsController(
+ IEventRepository eventRepository,
+ ICipherRepository cipherRepository,
+ CurrentContext currentContext)
+ {
+ _eventRepository = eventRepository;
+ _cipherRepository = cipherRepository;
+ _currentContext = currentContext;
+ }
+
+ ///
+ /// List all events.
+ ///
+ ///
+ /// Returns a filtered list of your organization's event logs, paged by a continuation token.
+ /// If no filters are provided, it will return the last 30 days of event for the organization.
+ ///
+ [HttpGet]
+ [ProducesResponseType(typeof(ListResponseModel), (int)HttpStatusCode.OK)]
+ public async Task List([FromQuery]EventFilterRequestModel request)
+ {
+ var dateRange = request.ToDateRange();
+ var result = new PagedResult();
+ if(request.ActingUserId.HasValue)
+ {
+ result = await _eventRepository.GetManyByOrganizationActingUserAsync(
+ _currentContext.OrganizationId.Value, request.ActingUserId.Value, dateRange.Item1, dateRange.Item2,
+ new PageOptions { ContinuationToken = request.ContinuationToken });
+ }
+ else if(request.ItemId.HasValue)
+ {
+ var cipher = await _cipherRepository.GetByIdAsync(request.ItemId.Value);
+ if(cipher != null && cipher.OrganizationId == _currentContext.OrganizationId.Value)
+ {
+ result = await _eventRepository.GetManyByCipherAsync(
+ cipher, dateRange.Item1, dateRange.Item2,
+ new PageOptions { ContinuationToken = request.ContinuationToken });
+ }
+ }
+ else
+ {
+ result = await _eventRepository.GetManyByOrganizationAsync(
+ _currentContext.OrganizationId.Value, dateRange.Item1, dateRange.Item2,
+ new PageOptions { ContinuationToken = request.ContinuationToken });
+ }
+
+ var eventResponses = result.Data.Select(e => new EventResponseModel(e));
+ var response = new ListResponseModel(eventResponses);
+ return new JsonResult(response);
+ }
+ }
+}
diff --git a/src/Core/Models/Api/Public/Request/EventFilterRequestModel.cs b/src/Core/Models/Api/Public/Request/EventFilterRequestModel.cs
new file mode 100644
index 0000000000..5d1f724568
--- /dev/null
+++ b/src/Core/Models/Api/Public/Request/EventFilterRequestModel.cs
@@ -0,0 +1,51 @@
+using System;
+using Bit.Core.Exceptions;
+
+namespace Bit.Core.Models.Api.Public
+{
+ public class EventFilterRequestModel
+ {
+ ///
+ /// The start date. Must be less than the end date.
+ ///
+ public DateTime? Start { get; set; }
+ ///
+ /// The end date. Must be greater than the start date.
+ ///
+ public DateTime? End { get; set; }
+ ///
+ /// The unique identifier of the user that performed the event.
+ ///
+ public Guid? ActingUserId { get; set; }
+ ///
+ /// The unique identifier of the related item that the event describes.
+ ///
+ public Guid? ItemId { get; set; }
+ ///
+ /// A cursor for use in pagination.
+ ///
+ public string ContinuationToken { get; set; }
+
+ public Tuple ToDateRange()
+ {
+ if(!End.HasValue || !Start.HasValue)
+ {
+ End = DateTime.UtcNow.Date.AddDays(1).AddMilliseconds(-1);
+ Start = DateTime.UtcNow.Date.AddDays(-30);
+ }
+ else if(Start.Value > End.Value)
+ {
+ var newEnd = Start;
+ Start = End;
+ End = newEnd;
+ }
+
+ if((End.Value - Start.Value) > TimeSpan.FromDays(367))
+ {
+ throw new BadRequestException("Date range must be < 367 days.");
+ }
+
+ return new Tuple(Start.Value, End.Value);
+ }
+ }
+}
diff --git a/src/Core/Models/Api/Public/Response/EventResponseModel.cs b/src/Core/Models/Api/Public/Response/EventResponseModel.cs
new file mode 100644
index 0000000000..c9575151e1
--- /dev/null
+++ b/src/Core/Models/Api/Public/Response/EventResponseModel.cs
@@ -0,0 +1,82 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using Bit.Core.Enums;
+using Bit.Core.Models.Data;
+
+namespace Bit.Core.Models.Api.Public
+{
+ ///
+ /// An event log.
+ ///
+ public class EventResponseModel : IResponseModel
+ {
+ public EventResponseModel(IEvent ev)
+ {
+ if(ev == null)
+ {
+ throw new ArgumentNullException(nameof(ev));
+ }
+
+ Type = ev.Type;
+ ItemId = ev.CipherId;
+ CollectionId = ev.CollectionId;
+ GroupId = ev.GroupId;
+ MemberId = ev.OrganizationUserId;
+ ActingUserId = ev.ActingUserId;
+ Date = ev.Date;
+ Device = ev.DeviceType;
+ IpAddress = ev.IpAddress;
+ }
+
+ ///
+ /// String representing the object's type. Objects of the same type share the same properties.
+ ///
+ /// event
+ [Required]
+ public string Object => "event";
+ ///
+ /// The type of event.
+ ///
+ [Required]
+ public EventType Type { get; set; }
+ ///
+ /// The unique identifier of the related item that the event describes.
+ ///
+ /// 3767a302-8208-4dc6-b842-030428a1cfad
+ public Guid? ItemId { get; set; }
+ ///
+ /// The unique identifier of the related collection that the event describes.
+ ///
+ /// bce212a4-25f3-4888-8a0a-4c5736d851e0
+ public Guid? CollectionId { get; set; }
+ ///
+ /// The unique identifier of the related group that the event describes.
+ ///
+ /// f29a2515-91d2-4452-b49b-5e8040e6b0f4
+ public Guid? GroupId { get; set; }
+ ///
+ /// The unique identifier of the related member that the event describes.
+ ///
+ /// e68b8629-85eb-4929-92c0-b84464976ba4
+ public Guid? MemberId { get; set; }
+ ///
+ /// The unique identifier of the user that performed the event.
+ ///
+ /// a2549f79-a71f-4eb9-9234-eb7247333f94
+ public Guid? ActingUserId { get; set; }
+ ///
+ /// The date/timestamp when the event occurred.
+ ///
+ [Required]
+ public DateTime Date { get; set; }
+ ///
+ /// The type of device used by the acting user when the event occurred.
+ ///
+ public DeviceType? Device { get; set; }
+ ///
+ /// The IP address of the acting user.
+ ///
+ /// 172.16.254.1
+ public string IpAddress { get; set; }
+ }
+}