{"openapi":"3.1.0","info":{"title":"Image Portal Sales API","description":"API for managing media download transactions across image portal services","version":"1.0.0"},"servers":[{"url":"https://sales.api.im.test.ntb.dev","description":"Server URL in Test environment"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Suppliers","description":"Manage the companies whose content we credit and invoice"},{"name":"Supplier mappings","description":"Manage the rules that attribute incoming assets to a supplier"},{"name":"Distributor Config","description":"Read the distributor configuration"},{"name":"Download Logs","description":"Record media download events"}],"paths":{"/api/v1/suppliers":{"get":{"tags":["Suppliers"],"summary":"List suppliers","description":"Returns a paginated list of suppliers in the caller's tenant. Search is a case-insensitive substring match on name or Visma id; sort by name, created or updated, in either direction. With includeMappings=true each supplier carries its full mappings array, so the admin table renders in one call. Pagination is always over suppliers, never over mappings.","operationId":"listSuppliers","parameters":[{"name":"search","in":"query","description":"Case-insensitive substring match on name or Visma id","required":false,"schema":{"type":"string"}},{"name":"sort","in":"query","description":"Field to sort by","required":false,"schema":{"type":"string","enum":["name","createdAt","updatedAt"]}},{"name":"sortDir","in":"query","description":"Sort direction","required":false,"schema":{"type":"string","enum":["asc","desc"]}},{"name":"includeMappings","in":"query","description":"When true, each supplier carries its full mappings array. Off by default, so callers that only need supplier details do not pay for rules they will not render.","required":false,"schema":{"type":"boolean"}},{"name":"page","in":"query","description":"Page number (1-based)","required":false,"schema":{"type":"integer","format":"int32","minimum":1},"example":1},{"name":"limit","in":"query","description":"Number of items per page (1-100)","required":false,"schema":{"type":"integer","format":"int32","maximum":100,"minimum":1},"example":20}],"responses":{"400":{"description":"Invalid filter, sort or pagination parameters","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable Content","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"Suppliers retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListResponseSupplierResponse"}}}}}},"post":{"tags":["Suppliers"],"summary":"Create a supplier","description":"Creates a supplier under the caller's tenant. The body carries no id, no tenantId and no timestamps; all are server-assigned. As on get, the ETag is omitted when includeMappings=true.","operationId":"createSupplier","parameters":[{"name":"includeMappings","in":"query","description":"Include this supplier's mapping rules (always empty on create)","required":false,"schema":{"type":"boolean","default":false}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSupplierRequest"}}},"required":true},"responses":{"400":{"description":"Field validation failed","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable Content","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Organisation number already exists in the tenant","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"201":{"description":"Supplier created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetResponseSupplierResponse"}}}}}}},"/api/v1/supplier-mappings":{"get":{"tags":["Supplier mappings"],"summary":"List supplier mappings","description":"Returns a paginated, flat list of every mapping rule in the caller's tenant, regardless of owner. Filter to one supplier with supplierId. Search is a case-insensitive substring match on the match field, match value or extra condition; sort by match value, created or updated, in either direction. Each row carries its supplier's name and Visma id, so the admin table needs no second call.","operationId":"listSupplierMappings","parameters":[{"name":"supplierId","in":"query","description":"Restrict to the rules owned by one supplier","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"search","in":"query","description":"Case-insensitive substring match on the match field, match value or extra condition","required":false,"schema":{"type":"string"}},{"name":"sort","in":"query","description":"Field to sort by","required":false,"schema":{"type":"string","enum":["primaryMappingValue","createdAt","updatedAt"]}},{"name":"sortDir","in":"query","description":"Sort direction","required":false,"schema":{"type":"string","enum":["asc","desc"]}},{"name":"page","in":"query","description":"Page number (1-based)","required":false,"schema":{"type":"integer","format":"int32","minimum":1},"example":1},{"name":"limit","in":"query","description":"Number of items per page (1-100)","required":false,"schema":{"type":"integer","format":"int32","maximum":100,"minimum":1},"example":20}],"responses":{"400":{"description":"Invalid filter, sort or pagination parameters","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable Content","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"Mappings retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListResponseSupplierMappingResponse"}}}}}},"post":{"tags":["Supplier mappings"],"summary":"Create a supplier mapping","description":"Creates a rule under the caller's tenant. The supplier is carried in the body and is immutable once set. An unknown supplier, or one in another tenant, is a 404; a rule duplicating an existing active one — same field, value and extra condition, even under a different supplier — is a 409.","operationId":"createSupplierMapping","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSupplierMappingRequest"}}},"required":true},"responses":{"400":{"description":"Field validation failed","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such supplier in the caller's tenant","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable Content","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"An identical rule already exists in the tenant","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"201":{"description":"Mapping created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetResponseSupplierMappingResponse"}}}}}}},"/api/v1/download-logs":{"get":{"tags":["Download Logs"],"summary":"List download logs","description":"Returns a paginated list of download logs, scoped to the caller's tenant.","operationId":"list","parameters":[{"name":"id","in":"query","description":"Filter by download log ID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"userEmail","in":"query","description":"Filter by user email (case-insensitive)","required":false,"schema":{"type":"string","format":"email"}},{"name":"page","in":"query","description":"Page number (1-based)","required":false,"schema":{"type":"integer","format":"int32","minimum":1},"example":1},{"name":"limit","in":"query","description":"Number of items per page (1-100)","required":false,"schema":{"type":"integer","format":"int32","maximum":100,"minimum":1},"example":20}],"responses":{"400":{"description":"Invalid filter or pagination parameters","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable Content","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"Download logs retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListResponseDownloadLogResponse"}}}}}},"post":{"tags":["Download Logs"],"summary":"Record a download","description":"Persists a new download log entry under the caller's tenant and returns the created record","operationId":"recordDownload","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDownloadLogRequest"}}},"required":true},"responses":{"400":{"description":"Invalid request body","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable Content","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"tenantId in the request does not match the authenticated portal user's tenant","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"201":{"description":"Download log created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetResponseDownloadLogResponse"}}}}}}},"/api/v1/suppliers/{id}":{"get":{"tags":["Suppliers"],"summary":"Get a supplier","description":"Returns one supplier and the ETag to send back in a later If-Match. The ETag is omitted when includeMappings=true, since it versions the supplier only. An unknown id, another tenant's supplier and a deleted supplier are all 404.","operationId":"getSupplier","parameters":[{"name":"id","in":"path","description":"The ID of the supplier","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"includeMappings","in":"query","description":"Include this supplier's mapping rules","required":false,"schema":{"type":"boolean","default":false}}],"responses":{"400":{"description":"Bad Request","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such supplier in the caller's tenant","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable Content","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"Supplier found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetResponseSupplierResponse"}}}}}},"delete":{"tags":["Suppliers"],"summary":"Delete a supplier","description":"Soft-deletes a supplier: it drops out of every read and its organisation number can be used again. The row is kept, so download logs that reference it still resolve. A supplier that still has active mappings is a 409; its mappings are never deleted with it, so delete them first. An unknown, already deleted or other tenant's supplier is a 404, so a repeated delete is a 404. Send the ETag from the last read as If-Match.","operationId":"deleteSupplier","parameters":[{"name":"id","in":"path","description":"The ID of the supplier","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"If-Match","in":"header","description":"ETag from the last read of this supplier","required":true,"schema":{"type":"string"}}],"responses":{"400":{"description":"Malformed If-Match","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such supplier in the caller's tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable Content","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The supplier still has active mappings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"204":{"description":"Supplier deleted"},"412":{"description":"Stale If-Match; the supplier has changed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"428":{"description":"If-Match header missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"tags":["Suppliers"],"summary":"Update a supplier","description":"Applies a partial update. Omitted fields are left unchanged; an empty string clears address or contactEmail, but not name, vismaId or organizationNumber. Mappings are not accepted here — rules are managed through their own endpoints. Send the ETag from the last read as If-Match.","operationId":"patchSupplier","parameters":[{"name":"id","in":"path","description":"The ID of the supplier","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"If-Match","in":"header","description":"ETag from the last read of this supplier","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchSupplierRequest"}}},"required":true},"responses":{"400":{"description":"Field validation failed (e.g. bad email), or malformed If-Match","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such supplier in the caller's tenant","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Empty patch or a blank mandatory field","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Organisation number already exists in the tenant","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"Supplier updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetResponseSupplierResponse"}}}},"412":{"description":"Stale If-Match; the supplier has changed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"428":{"description":"If-Match header missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/supplier-mappings/{id}":{"get":{"tags":["Supplier mappings"],"summary":"Get a supplier mapping","description":"Returns one mapping rule and the ETag to send back in a later If-Match, denormalised with its supplier's name and Visma id. An unknown id, another tenant's mapping and a deleted mapping are all 404.","operationId":"getSupplierMapping","parameters":[{"name":"id","in":"path","description":"The ID of the mapping","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"400":{"description":"Bad Request","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such mapping in the caller's tenant","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable Content","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"Mapping found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetResponseSupplierMappingResponse"}}}}}},"delete":{"tags":["Supplier mappings"],"summary":"Delete a supplier mapping","description":"Soft-deletes a rule: it stops attributing assets and drops out of every read, and its field, value and extra condition can be used again by any supplier. The row is kept for history. An unknown, already deleted or other tenant's mapping is a 404, so a repeated delete is a 404; two deletes sent at the same moment can instead give the second a 412, after which a re-read is a 404. Send the ETag from the last read as If-Match.","operationId":"deleteSupplierMapping","parameters":[{"name":"id","in":"path","description":"The ID of the mapping","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"If-Match","in":"header","description":"ETag from the last read of this mapping","required":true,"schema":{"type":"string"}}],"responses":{"400":{"description":"Malformed If-Match","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such mapping in the caller's tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable Content","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"204":{"description":"Mapping deleted"},"412":{"description":"Stale If-Match; the mapping has changed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"428":{"description":"If-Match header missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"tags":["Supplier mappings"],"summary":"Update a supplier mapping","description":"Applies a partial update. Omitted fields are left unchanged; an empty string clears the extra condition, turning the rule back into the fallback rule, but not the match field or value. The supplier cannot be changed: send the current supplierId or leave it out, and delete and recreate the rule to move it. An edit that makes the rule duplicate an existing active one is a 409. Send the ETag from the last read as If-Match.","operationId":"patchSupplierMapping","parameters":[{"name":"id","in":"path","description":"The ID of the mapping","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"If-Match","in":"header","description":"ETag from the last read of this mapping","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchSupplierMappingRequest"}}},"required":true},"responses":{"400":{"description":"Field validation failed (e.g. empty mediaFamily), or malformed If-Match","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such mapping in the caller's tenant","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Empty patch, a blank match field or value, or a changed supplierId","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"An identical rule already exists in the tenant","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"Mapping updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetResponseSupplierMappingResponse"}}}},"412":{"description":"Stale If-Match; the mapping has changed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"428":{"description":"If-Match header missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/supplier-mappings/rules":{"get":{"tags":["Supplier mappings"],"summary":"List active mapping rules for the indexer","description":"Returns every active mapping rule of an active supplier, unpaged, for attributing incoming assets to suppliers. Send X-Tenant-Id for one tenant's rules; without it, rules for all tenants are returned, each carrying its tenantId. The extra condition is not validated here and is null for a fallback rule. Order is stable between calls. Service (M2M) callers only.","operationId":"listSupplierMappingRules","parameters":[{"name":"X-Tenant-Id","in":"header","description":"Only return rules of this tenant; omit for all tenants","schema":{"type":"string","format":"uuid"}}],"responses":{"400":{"description":"Malformed X-Tenant-Id header","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable Content","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Caller is not a service (M2M) client","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"Rules retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListResponseSupplierMappingRule"}}}}}}},"/api/v1/distributor-config":{"get":{"tags":["Distributor Config"],"summary":"List distributor configuration entries","description":"Returns the full distributor configuration","operationId":"list_1","responses":{"400":{"description":"Bad Request","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable Content","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"Distributor configuration retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListResponseDistributorConfigEntry"}}}}}}}},"components":{"schemas":{"APIFieldError":{"type":"object","properties":{"fieldName":{"type":"string"},"fieldError":{"type":"string"}}},"APIMeta":{"type":"object","properties":{"description":{"type":"string"},"version":{"type":"string"}}},"APIStatusError":{"type":"object","properties":{"message":{"type":"string"},"statusCode":{"type":"integer","format":"int32"},"timestamp":{"type":"string"},"path":{"type":"string"},"success":{"type":"boolean"},"apiFieldErrors":{"type":"array","items":{"$ref":"#/components/schemas/APIFieldError"},"uniqueItems":true}}},"ErrorResponse":{"type":"object","properties":{"_status":{"$ref":"#/components/schemas/APIStatusError"},"_meta":{"$ref":"#/components/schemas/APIMeta"}}},"CreateSupplierRequest":{"type":"object","description":"Details of a supplier to create","properties":{"name":{"type":"string","description":"Display name of the supplier","maxLength":255,"minLength":1},"vismaId":{"type":"string","description":"Visma identifier used for invoicing. No format rule, and not unique.","maxLength":50,"minLength":0},"organizationNumber":{"type":"string","description":"Organisation number (organisasjonsnummer). Required, unique per tenant.","maxLength":50,"minLength":0},"address":{"type":"string","description":"Postal address; may be null","maxLength":1000,"minLength":0},"contactEmail":{"type":"string","format":"email","description":"Contact email; may be null","maxLength":254,"minLength":0}},"required":["name","organizationNumber","vismaId"]},"GetResponseSupplierResponse":{"type":"object","properties":{"_meta":{"$ref":"#/components/schemas/Meta"},"_data":{"$ref":"#/components/schemas/SupplierResponse"}}},"Meta":{"type":"object","properties":{"description":{"type":"string"},"version":{"type":"string"}}},"SupplierMappingResponse":{"type":"object","description":"A rule attributing matching assets to a supplier","properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier of the mapping"},"supplierId":{"type":"string","format":"uuid","description":"Supplier this rule attributes matching assets to. Immutable once set."},"supplierName":{"type":"string","description":"Name of the owning supplier; omitted when nested under that supplier"},"supplierVismaId":{"type":"string","description":"Visma id of the owning supplier; omitted when nested under that supplier"},"primaryMappingField":{"type":"string","description":"Asset metadata field this rule matches on"},"primaryMappingValue":{"type":"string","description":"Value that field must equal"},"mediaFamily":{"type":"array","description":"Licensing families this rule covers","items":{"type":"string","enum":["editorial","creative"]}},"secondaryMappingQuery":{"type":"string","description":"Query narrowing the match; an empty string means this is the fallback rule"},"version":{"type":"integer","format":"int32","description":"Optimistic lock counter; the value returned in the ETag header"},"createdAt":{"type":"string","format":"date-time","description":"When the mapping was created"},"updatedAt":{"type":"string","format":"date-time","description":"When the mapping was last changed"}},"required":["createdAt","id","mediaFamily","primaryMappingField","primaryMappingValue","supplierId","updatedAt","version"]},"SupplierResponse":{"type":"object","description":"A supplier of photo or video content","properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier of the supplier"},"name":{"type":"string","description":"Display name"},"vismaId":{"type":"string","description":"Visma identifier used for invoicing"},"organizationNumber":{"type":"string","description":"Organisation number (organisasjonsnummer)"},"address":{"type":["string","null"],"description":"Postal address"},"contactEmail":{"type":["string","null"],"description":"Contact email"},"mappings":{"type":"array","description":"This supplier's mapping rules. Present only when includeMappings=true; empty when the supplier has none.","items":{"$ref":"#/components/schemas/SupplierMappingResponse"}},"version":{"type":"integer","format":"int32","description":"Optimistic lock counter; the value returned in the ETag header"},"createdAt":{"type":"string","format":"date-time","description":"When the supplier was created"},"updatedAt":{"type":"string","format":"date-time","description":"When the supplier was last changed"}},"required":["createdAt","id","name","organizationNumber","updatedAt","version","vismaId"]},"CreateSupplierMappingRequest":{"type":"object","description":"Details of a supplier mapping rule to create","properties":{"supplierId":{"type":"string","format":"uuid","description":"Supplier this rule attributes matching assets to. Immutable once set."},"primaryMappingField":{"type":"string","description":"Asset metadata field this rule matches on, e.g. credit or byline","minLength":1},"primaryMappingValue":{"type":"string","description":"Value that field must equal","minLength":1},"mediaFamily":{"type":"array","description":"Licensing families this rule covers; at least one of editorial or creative","items":{"type":"string","enum":["editorial","creative"]},"minItems":1},"secondaryMappingQuery":{"type":"string","description":"Lucene query narrowing the match. Omit, send null or send \"\" for the fallback rule with no extra condition."}},"required":["mediaFamily","primaryMappingField","primaryMappingValue","supplierId"]},"GetResponseSupplierMappingResponse":{"type":"object","properties":{"_meta":{"$ref":"#/components/schemas/Meta"},"_data":{"$ref":"#/components/schemas/SupplierMappingResponse"}}},"CreateDownloadLogRequest":{"type":"object","description":"Request body for recording a media download","properties":{"userId":{"type":"string","format":"uuid","description":"ID of the user who performed the download"},"userEmail":{"type":"string","format":"email","description":"Email of the user who performed the download","minLength":1},"tenantId":{"type":"string","format":"uuid","description":"ID of the tenant"},"workspaceId":{"type":"string","format":"uuid","description":"ID of the workspace"},"enterpriseOrgId":{"type":"string","format":"uuid","description":"ID of the enterprise organization; optional"},"currentPrice":{"type":"number","description":"Price charged at time of download"},"currency":{"type":"string","description":"Currency code of the price (e.g. NOK, USD)","minLength":1},"purchaseType":{"type":"string","description":"Type of purchase","enum":["SUBSCRIPTION","PACKED","PAYMENT_GATEWAY"]},"supplierId":{"type":"string","format":"uuid","description":"ID of the supplier"},"mediaArchive":{"type":"string","description":"Archive type of the media","enum":["sdl","shutterStock"]},"media":{"$ref":"#/components/schemas/MediaSnapshot","description":"Media metadata as a JSON object"},"customerGroupId":{"type":"string","format":"uuid","description":"ID of the customer group"},"productId":{"type":"string","format":"uuid","description":"ID of the product"}},"required":["currency","currentPrice","customerGroupId","media","mediaArchive","productId","purchaseType","supplierId","tenantId","userEmail","userId","workspaceId"]},"MediaSnapshot":{"type":"object","properties":{"id":{"type":"string"},"mediaFamily":{"type":"string"},"mediaType":{"type":"string"},"dateCreated":{"type":"string","format":"date-time"},"dateArchived":{"type":"string","format":"date-time"},"headline":{"type":"string"},"description":{"type":"string"},"credit":{"type":"string"},"distributor":{"type":"string"},"creator":{"type":"string"},"city":{"type":"string"},"countryName":{"type":"string"},"provinceState":{"type":"string"},"datePosted":{"type":"string","format":"date-time"},"owner":{"type":"string"},"category":{"type":"string"},"jobid":{"type":"string"},"source":{"type":"string"},"instructions":{"type":"string"},"supplementalCategory":{"type":"string"},"supplier":{"type":"string"},"rating":{"type":"number","format":"double"},"usageTerms":{"type":"string"},"imageSupplierImageId":{"type":"string"},"originalFilename":{"type":"string"},"title":{"type":"string"},"archive":{"type":"string"}}},"DownloadLogResponse":{"type":"object","description":"Details of a recorded media download","properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier of the download log record"},"userId":{"type":"string","format":"uuid","description":"ID of the user who performed the download"},"userEmail":{"type":"string","description":"Email of the user who performed the download"},"tenantId":{"type":"string","format":"uuid","description":"ID of the tenant"},"workspaceId":{"type":"string","format":"uuid","description":"ID of the workspace"},"enterpriseOrgId":{"type":"string","format":"uuid","description":"ID of the enterprise organization; may be null"},"currentPrice":{"type":"number","description":"Price charged at time of download"},"currency":{"type":"string","description":"Currency code of the price (e.g. NOK, USD)"},"purchaseType":{"type":"string","description":"Type of purchase","enum":["SUBSCRIPTION","PACKED","PAYMENT_GATEWAY"]},"supplierId":{"type":"string","format":"uuid","description":"ID of the supplier"},"mediaId":{"type":"string","description":"ID of the media asset"},"mediaType":{"type":"string","description":"Type of media","enum":["image","video"]},"mediaArchive":{"type":"string","description":"Archive type of the media","enum":["sdl","shutterStock"]},"creator":{"type":"string","description":"Creator of the media, from the media metadata"},"distributor":{"type":"string","description":"Distributor of the media, from the media metadata"},"supplier":{"type":"string","description":"Supplier of the media, from the media metadata"},"customerGroupId":{"type":"string","format":"uuid","description":"ID of the customer group"},"productId":{"type":"string","format":"uuid","description":"ID of the product"},"downloadedAt":{"type":"string","format":"date-time","description":"Timestamp of the most recent download"},"downloadCount":{"type":"integer","format":"int32","description":"Total number of times this media has been downloaded by the user"}},"required":["currency","currentPrice","customerGroupId","downloadCount","downloadedAt","id","mediaArchive","mediaId","mediaType","productId","purchaseType","supplierId","tenantId","userEmail","userId","workspaceId"]},"GetResponseDownloadLogResponse":{"type":"object","properties":{"_meta":{"$ref":"#/components/schemas/Meta"},"_data":{"$ref":"#/components/schemas/DownloadLogResponse"}}},"PatchSupplierRequest":{"type":"object","description":"Fields to change on a supplier; omitted fields are left unchanged","properties":{"name":{"type":"string","description":"New display name. Must be non-blank if present.","maxLength":255,"minLength":0},"vismaId":{"type":"string","description":"New Visma identifier. Must be non-blank if present.","maxLength":50,"minLength":0},"organizationNumber":{"type":"string","description":"New organisation number. Must be non-blank if present.","maxLength":50,"minLength":0},"address":{"type":"string","description":"New postal address. Send \"\" to clear.","maxLength":1000,"minLength":0},"contactEmail":{"type":"string","format":"email","description":"New contact email. Send \"\" to clear.","maxLength":254,"minLength":0}}},"PatchSupplierMappingRequest":{"type":"object","description":"Fields to change on a supplier mapping; omitted fields are left unchanged","properties":{"supplierId":{"type":"string","format":"uuid","description":"Must be the mapping's current supplier if present. A rule cannot be moved to another supplier; delete it and create it again instead."},"primaryMappingField":{"type":"string","description":"New asset metadata field to match on. Must be non-blank if present."},"primaryMappingValue":{"type":"string","description":"New value that field must equal. Must be non-blank if present."},"mediaFamily":{"type":"array","description":"New licensing families; at least one of editorial or creative if present","items":{"type":"string","enum":["editorial","creative"]},"maxItems":2,"minItems":1},"secondaryMappingQuery":{"type":"string","description":"New Lucene query narrowing the match. Send \"\" to clear it."}}},"ListResponseSupplierResponse":{"type":"object","properties":{"_meta":{"$ref":"#/components/schemas/Meta"},"_data":{"type":"array","items":{"$ref":"#/components/schemas/SupplierResponse"}},"_page":{"$ref":"#/components/schemas/Page"}}},"Page":{"type":"object","properties":{"offset":{"type":"integer","format":"int32"},"limit":{"type":"integer","format":"int32"},"total":{"type":"integer","format":"int64"}}},"ListResponseSupplierMappingResponse":{"type":"object","properties":{"_meta":{"$ref":"#/components/schemas/Meta"},"_data":{"type":"array","items":{"$ref":"#/components/schemas/SupplierMappingResponse"}},"_page":{"$ref":"#/components/schemas/Page"}}},"ListResponseSupplierMappingRule":{"type":"object","properties":{"_meta":{"$ref":"#/components/schemas/Meta"},"_data":{"type":"array","items":{"$ref":"#/components/schemas/SupplierMappingRule"}},"_page":{"$ref":"#/components/schemas/Page"}}},"SupplierMappingRule":{"type":"object","description":"Active supplier mapping rule, for attributing incoming assets to a supplier","properties":{"tenantId":{"type":"string","format":"uuid","description":"Tenant the rule belongs to"},"id":{"type":"string","format":"uuid","description":"Mapping rule id"},"supplierId":{"type":"string","format":"uuid","description":"Supplier the rule attributes matching assets to"},"supplierName":{"type":"string","description":"Name of that supplier"},"supplierVismaId":{"type":"string","description":"Visma id of that supplier"},"primaryMappingField":{"type":"string","description":"Asset metadata field the rule matches on, e.g. credit"},"primaryMappingValue":{"type":"string","description":"Value that field must equal"},"secondaryMappingQuery":{"type":["string","null"],"description":"Lucene query narrowing the match; null for the fallback rule of that field and value. Not validated by this service."},"mediaFamily":{"type":"array","description":"Licensing families the rule covers","items":{"type":"string","enum":["editorial","creative"]}}},"required":["id","mediaFamily","primaryMappingField","primaryMappingValue","supplierId","supplierName","supplierVismaId","tenantId"]},"ListResponseDownloadLogResponse":{"type":"object","properties":{"_meta":{"$ref":"#/components/schemas/Meta"},"_data":{"type":"array","items":{"$ref":"#/components/schemas/DownloadLogResponse"}},"_page":{"$ref":"#/components/schemas/Page"}}},"DistributorConfigEntry":{"type":"object","description":"Distributor configuration entry","properties":{"id":{"type":"integer","format":"int32","description":"Distributor ID; shared by entries with the same name"},"credit":{"type":"string","description":"Distributor credit line"},"name":{"type":"string","description":"Distributor name"},"condition":{"type":"string","description":"Lucene query condition used to match media to this entry; may be null"},"mediaFamily":{"type":"string","description":"Media family this entry applies to","example":"editorial"}},"required":["credit","id","mediaFamily","name"]},"ListResponseDistributorConfigEntry":{"type":"object","properties":{"_meta":{"$ref":"#/components/schemas/Meta"},"_data":{"type":"array","items":{"$ref":"#/components/schemas/DistributorConfigEntry"}},"_page":{"$ref":"#/components/schemas/Page"}}}},"securitySchemes":{"bearerAuth":{"type":"http","name":"Authorization","in":"header","scheme":"bearer","bearerFormat":"JWT"}}}}