API Reference

Holdings API

Complete reference for the Holdings API endpoints

The Holdings API provides access to HoldingCore2 holdings records. Holdings link zines to repositories, describing specific copies held in collections.

Endpoints

MethodEndpointDescription
GET/api/holdings/List all holdings (paginated)
POST/api/holdings/Create new holding
GET/api/holdings/{holding_id}/Get specific holding
PUT/api/holdings/{holding_id}/Replace holding (all fields)
PATCH/api/holdings/{holding_id}/Update holding (partial)
DELETE/api/holdings/{holding_id}/Delete holding

Conceptual Model

Holdings represent specific copies of zines held by repositories.

Relationship:

Holding → Zine (which zine)
Holding → Repository (where held)

Example:

  • Zine: "Mutate Zine #3"
  • Repository: "Barnard Zine Library"
  • Holding: "The copy of Mutate #3 held at Barnard, shelf A-12, excellent condition"

Multiple repositories can hold the same zine (multiple holding records).


List Holdings

Get paginated list of all holdings.

Request

GET /api/holdings/

Authentication: Not required (public endpoint)

Query parameters:

  • page — Page number (default: 1)
  • page_size — Results per page (default: 25, max: 100)
  • zine_id — Filter by zine external ID
  • repository_id — Filter by repository external ID
  • access_status — Filter by access status code
  • digital_available — Filter by digital availability (true/false)
  • ordering — Sort field

Response

{
  "count": 3421,
  "next": "http://localhost:8000/api/holdings/?page=2",
  "previous": null,
  "results": [
    {
      "holding_id": "holding_barnard_mutate_3",
      "zine": {
        "zine_id": "zine_mutate_3_1st",
        "title": "Mutate Zine #3: Abortion Stories"
      },
      "repository": {
        "repo_id": "repo_barnard_zine_library",
        "repository_name": "Barnard Zine Library"
      },
      "location": "Shelf A-12",
      "access_status": "reading-room-only",
      "condition": "Excellent",
      "copy_count": 1,
      "digital_available": false,
      "created_at": "2024-02-25T11:00:00Z"
    }
  ]
}

Note: Read responses include full nested objects for zine and repository.

Examples

Basic list:

curl http://localhost:8000/api/holdings/

All holdings of a specific zine:

curl "http://localhost:8000/api/holdings/?zine_id=zine_mutate_3_1st"

All holdings at a repository:

curl "http://localhost:8000/api/holdings/?repository_id=repo_barnard_zine_library"

Holdings with digital versions:

curl "http://localhost:8000/api/holdings/?digital_available=true"

Combine filters:

curl "http://localhost:8000/api/holdings/?repository_id=repo_barnard_zine_library&digital_available=true"

Get Holding

Retrieve a specific holding by external ID.

Request

GET /api/holdings/{holding_id}/

Authentication: Not required (public endpoint)

URL parameters:

  • holding_id — Holding's external identifier

Response

{
  "holding_id": "holding_barnard_mutate_3",
  "zine": {
    "zine_id": "zine_mutate_3_1st",
    "title": "Mutate Zine #3: Abortion Stories",
    "creator": [
      {
        "agent_id": "agent_judith_arcana",
        "display_name": "Judith Arcana"
      }
    ],
    "date": ["2024"]
  },
  "repository": {
    "repo_id": "repo_barnard_zine_library",
    "repository_name": "Barnard Zine Library",
    "city": "New York",
    "country": "US",
    "website": "https://zines.barnard.edu"
  },
  "call_number": "ZINE-FEM-2024-001",
  "location": "Shelf A-12, Feminism Section",
  "access_status": "reading-room-only",
  "condition": "Excellent. Minor cover wear.",
  "copy_count": 1,
  "barcode": "300123456789",
  "digital_available": false,
  "digital_url": "",
  "distro_status": "no-duplication",
  "notes": ["Acquired directly from author at Ladyfest 2024"],
  "created_at": "2024-02-25T11:00:00Z",
  "updated_at": "2024-02-25T11:00:00Z"
}

Nested objects:

  • zine — Full zine object (partial shown above)
  • repository — Full repository object

Examples

Get holding:

curl http://localhost:8000/api/holdings/holding_barnard_mutate_3/

Get as JSON-LD:

curl -H "Accept: application/ld+json" \
  http://localhost:8000/api/holdings/holding_barnard_mutate_3/

Create Holding

Create a new holding record.

Request

POST /api/holdings/
Content-Type: application/json
Authorization: Token YOUR_TOKEN

Authentication: Required

Required Fields

FieldTypeDescriptionExample
holding_idstringUnique external identifier"holding_barnard_mutate_3"
zine_idstringZine's external ID"zine_mutate_3_1st"
repository_idstringRepository's external ID"repo_barnard_zine_library"

Note: Write serializer accepts zine_id and repository_id as strings, not nested objects.

Optional Fields

FieldTypeDescriptionExample
call_numberstringLocal call number"ZINE-FEM-2024-001"
locationstringShelf location"Shelf A-12"
access_statusstringAccess status code"reading-room-only"
conditionstringPhysical condition"Excellent"
copy_countintegerNumber of copies1
barcodestringILS barcode"300123456789"
digital_availablebooleanDigital version available?false
digital_urlstring (URL)URL to digital version""
distro_statusstringDuplication permissions"no-duplication"
notesarraystringAdditional notes["Acquired at event"]

Request Example

curl -X POST http://localhost:8000/api/holdings/ \
  -H "Authorization: Token YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "holding_id": "holding_barnard_mutate_3",
    "zine_id": "zine_mutate_3_1st",
    "repository_id": "repo_barnard_zine_library",
    "call_number": "ZINE-FEM-2024-001",
    "location": "Shelf A-12, Feminism Section",
    "access_status": "reading-room-only",
    "condition": "Excellent",
    "copy_count": 1,
    "digital_available": false,
    "notes": ["Acquired from author at Ladyfest 2024"]
  }'

Important: Use external IDs for zine_id and repository_id, not internal integer PKs.

Response (201 Created)

Returns the full holding object with nested zine and repository:

{
  "holding_id": "holding_barnard_mutate_3",
  "zine": {
    "zine_id": "zine_mutate_3_1st",
    "title": "Mutate Zine #3: Abortion Stories"
  },
  "repository": {
    "repo_id": "repo_barnard_zine_library",
    "repository_name": "Barnard Zine Library"
  },
  "call_number": "ZINE-FEM-2024-001",
  "location": "Shelf A-12, Feminism Section",
  "access_status": "reading-room-only",
  "condition": "Excellent",
  "copy_count": 1,
  "digital_available": false,
  "created_at": "2024-02-25T11:00:00Z",
  "updated_at": "2024-02-25T11:00:00Z"
}

Update Holding

Update an existing holding record.

PATCH (Partial Update)

PATCH /api/holdings/{holding_id}/
Content-Type: application/json
Authorization: Token YOUR_TOKEN

Request:

curl -X PATCH http://localhost:8000/api/holdings/holding_barnard_mutate_3/ \
  -H "Authorization: Token YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "condition": "Good. Minor cover wear and spine damage.",
    "digital_available": true,
    "digital_url": "https://archive.org/details/mutate-3"
  }'

Only provided fields are updated.

PUT (Full Replacement)

PUT /api/holdings/{holding_id}/
Content-Type: application/json
Authorization: Token YOUR_TOKEN

All required fields must be provided.

Response (200 OK)

Returns the updated full holding object.


Delete Holding

Delete a holding record.

Request

DELETE /api/holdings/{holding_id}/
Authorization: Token YOUR_TOKEN

Example:

curl -X DELETE http://localhost:8000/api/holdings/holding_barnard_mutate_3/ \
  -H "Authorization: Token YOUR_TOKEN"

Response (204 No Content)

Effect:

  • Holding record is deleted
  • Zine and repository are NOT deleted (only the relationship is removed)

Field Details

holding_id

Type: string (max 255 characters) Required: Yes Unique: Yes

External unique identifier for the holding.

Naming convention: holding_{repo_slug}_{zine_slug}_{copy}

Examples:

holding_barnard_mutate_3_001
holding_qzap_feminist_killjoy_1
holding_my_collection_punk_planet_73

zine_id

Type: string (zine external ID) Required: Yes Repeatable: No

External ID of the zine being held.

Write (POST/PUT/PATCH):

"zine_id": "zine_mutate_3_1st"

Read (GET response):

"zine": {
  "zine_id": "zine_mutate_3_1st",
  "title": "Mutate Zine #3"
}

Validation:

  • Zine must exist in the database
  • Returns 400 error if zine_id is invalid

repository_id

Type: string (repository external ID) Required: Yes Repeatable: No

External ID of the repository holding the zine.

Write:

"repository_id": "repo_barnard_zine_library"

Read:

"repository": {
  "repo_id": "repo_barnard_zine_library",
  "repository_name": "Barnard Zine Library"
}

Validation:

  • Repository must exist in the database
  • Returns 400 error if repository_id is invalid

call_number

Type: string (max 255 characters) Required: No Repeatable: No

Local call number or shelfmark.

Examples:

"ZINE-FEM-2024-001"
"A-12-F"
"ZC 2024.03"
""

location

Type: string (max 500 characters) Required: No Repeatable: No

Shelf location or sub-collection.

Examples:

"Shelf A-12, Feminism Section"
"Storage Room, Box 14"
"On display, January 2024"
"Digital only"
""

access_status

Type: string (max 100 characters, vocabulary code) Required: No Repeatable: No

Access/circulation status code.

Valid values:

  • circulating — Can be checked out
  • reading-room-only — In-house use only
  • restricted — Special permission required
  • digital-only — No physical access
  • personal-use — Personal collection, not available

See vocabulary: /api/vocabularies/access-status/

Examples:

"reading-room-only"
"circulating"
"restricted"
""

condition

Type: string (text field) Required: No Repeatable: No

Physical condition note.

Examples:

"Excellent"
"Good. Minor cover wear."
"Fair. Spine damage, pages intact."
"Poor. Missing pages 5-8."
""

copy_count

Type: integer Required: No Repeatable: No Default: 1

Number of copies this record represents.

Examples:

1
3
10

Use case: If a repository has 3 identical copies, you can create one holding record with copy_count: 3 instead of three separate records.

barcode

Type: string (max 255 characters) Required: No Repeatable: No

ILS barcode or item identifier.

Examples:

"300123456789"
"BC001234"
""

digital_available

Type: boolean Required: No Repeatable: No Default: false

Whether a digital version is available.

Examples:

true
false

digital_url

Type: string (URL format, max 255 characters) Required: No Repeatable: No

URL to digital version (if available).

Examples:

"https://archive.org/details/mutate-3"
"https://repository.example.edu/zines/mutate-3"
""

Validation: If digital_available: true, should provide digital_url.

distro_status

Type: string (max 100 characters) Required: No Repeatable: No

Duplication/digitization permissions code.

Examples:

"no-duplication"
"duplication-allowed"
"digitization-allowed"
"ask-permission"
""

notes

Type: array of strings (text fields) Required: No Repeatable: Yes

Additional holding-specific notes.

Examples:

["Acquired from author at Ladyfest 2024"]
["Gift from Jane Doe", "Signed by author"]
["Missing back cover"]
[]

Use Cases

Library Holding

Scenario: Academic library with ILS integration.

{
  "holding_id": "holding_barnard_mutate_3",
  "zine_id": "zine_mutate_3_1st",
  "repository_id": "repo_barnard_zine_library",
  "call_number": "ZINE-FEM-2024-001",
  "location": "Main Stacks, Shelf A-12",
  "access_status": "reading-room-only",
  "condition": "Excellent",
  "copy_count": 1,
  "barcode": "300123456789",
  "digital_available": false
}

Distro Holding

Scenario: Zine distro with circulating copies.

{
  "holding_id": "holding_qzap_mutate_3",
  "zine_id": "zine_mutate_3_1st",
  "repository_id": "repo_qzap",
  "location": "Lending Library",
  "access_status": "circulating",
  "condition": "Good",
  "copy_count": 5,
  "distro_status": "duplication-allowed",
  "notes": ["Available for free checkout with library card"]
}

Digital Archive Holding

Scenario: Digital-only archive.

{
  "holding_id": "holding_ia_mutate_3",
  "zine_id": "zine_mutate_3_1st",
  "repository_id": "repo_internet_archive",
  "location": "Digital only",
  "access_status": "digital-only",
  "digital_available": true,
  "digital_url": "https://archive.org/details/mutate-3",
  "notes": ["Scanned with permission from author"]
}

Personal Collection Holding

Scenario: Individual's personal zine collection.

{
  "holding_id": "holding_my_collection_mutate_3",
  "zine_id": "zine_mutate_3_1st",
  "repository_id": "repo_my_personal_collection",
  "location": "Bookshelf 2, Top Shelf",
  "access_status": "personal-use",
  "condition": "Excellent",
  "copy_count": 1,
  "notes": ["Purchased at Seattle Zine Fest 2024"]
}

Validation Errors

Missing Required Field

Response (400 Bad Request):

{
  "holding_id": ["This field is required."],
  "zine_id": ["This field is required."],
  "repository_id": ["This field is required."]
}

Invalid zine_id

Request:

{
  "zine_id": "zine_nonexistent"
}

Response (400 Bad Request):

{
  "zine_id": ["Zine 'zine_nonexistent' does not exist"]
}

Invalid repository_id

Request:

{
  "repository_id": "repo_nonexistent"
}

Response (400 Bad Request):

{
  "repository_id": ["Repository 'repo_nonexistent' does not exist"]
}

Code Examples

Python

import requests

API_BASE = "http://localhost:8000/api"
TOKEN = "YOUR_TOKEN"
headers = {
    "Authorization": f"Token {TOKEN}",
    "Content-Type": "application/json"
}

# Create holding
holding_data = {
    "holding_id": "holding_my_mutate_3",
    "zine_id": "zine_mutate_3_1st",
    "repository_id": "repo_my_collection",
    "location": "Bookshelf 2",
    "condition": "Excellent",
    "access_status": "personal-use"
}

response = requests.post(
    f"{API_BASE}/holdings/",
    headers=headers,
    json=holding_data
)

holding = response.json()
print(f"Created holding: {holding['zine']['title']} @ {holding['repository']['repository_name']}")

# Get all holdings for a zine
holdings = requests.get(
    f"{API_BASE}/holdings/?zine_id=zine_mutate_3_1st"
).json()

print(f"Found {holdings['count']} holdings")
for h in holdings['results']:
    print(f"  - {h['repository']['repository_name']}")

JavaScript

const API_BASE = "http://localhost:8000/api";
const TOKEN = "YOUR_TOKEN";

// Create holding
const holdingData = {
  holding_id: "holding_my_mutate_3",
  zine_id: "zine_mutate_3_1st",
  repository_id: "repo_my_collection",
  location: "Bookshelf 2",
  condition: "Excellent"
};

const response = await fetch(`${API_BASE}/holdings/`, {
  method: "POST",
  headers: {
    "Authorization": `Token ${TOKEN}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify(holdingData)
});

const holding = await response.json();
console.log(`Created: ${holding.zine.title} @ ${holding.repository.repository_name}`);

// Get holdings for a repository
const repoHoldings = await fetch(
  `${API_BASE}/holdings/?repository_id=repo_my_collection`
).then(r => r.json());

console.log(`Repository has ${repoHoldings.count} holdings`);

Next Steps

Copyright ©2026 ZineCore2 Contributors,