Holdings API
The Holdings API provides access to HoldingCore2 holdings records. Holdings link zines to repositories, describing specific copies held in collections.
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| 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
| Field | Type | Description | Example |
|---|---|---|---|
| holding_id | string | Unique external identifier | "holding_barnard_mutate_3" |
| zine_id | string | Zine's external ID | "zine_mutate_3_1st" |
| repository_id | string | Repository's external ID | "repo_barnard_zine_library" |
Note: Write serializer accepts zine_id and repository_id as strings, not nested objects.
Optional Fields
| Field | Type | Description | Example |
|---|---|---|---|
| call_number | string | Local call number | "ZINE-FEM-2024-001" |
| location | string | Shelf location | "Shelf A-12" |
| access_status | string | Access status code | "reading-room-only" |
| condition | string | Physical condition | "Excellent" |
| copy_count | integer | Number of copies | 1 |
| barcode | string | ILS barcode | "300123456789" |
| digital_available | boolean | Digital version available? | false |
| digital_url | string (URL) | URL to digital version | "" |
| distro_status | string | Duplication permissions | "no-duplication" |
| notes | arraystring | Additional 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
- Repositories API — Repositories endpoints reference
- Zines API — Zines endpoints reference
- Filtering — Advanced filtering and search